@graph8/sdk 0.361.0 → 0.362.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 (94) hide show
  1. package/blocks/_foundation/README.md +108 -0
  2. package/blocks/_foundation/block.json +160 -12
  3. package/blocks/_foundation/components/api-error.tsx +62 -18
  4. package/blocks/_foundation/components/empty-state.tsx +39 -7
  5. package/blocks/_foundation/components/screen/bar-list.tsx +71 -0
  6. package/blocks/_foundation/components/screen/detail-panel.tsx +114 -0
  7. package/blocks/_foundation/components/screen/entity-avatar.tsx +56 -0
  8. package/blocks/_foundation/components/screen/form-sheet.tsx +73 -0
  9. package/blocks/_foundation/components/screen/index.ts +21 -0
  10. package/blocks/_foundation/components/screen/key-value-list.tsx +65 -0
  11. package/blocks/_foundation/components/screen/screen-layout.tsx +124 -0
  12. package/blocks/_foundation/components/screen/section-card.tsx +47 -0
  13. package/blocks/_foundation/components/screen/sparkline.tsx +77 -0
  14. package/blocks/_foundation/components/screen/split-view.tsx +125 -0
  15. package/blocks/_foundation/components/screen/stat-strip.tsx +121 -0
  16. package/blocks/_foundation/components/screen/status-pill.tsx +104 -0
  17. package/blocks/_foundation/components/screen/time-series-chart.tsx +114 -0
  18. package/blocks/_foundation/components/screen/toolbar.tsx +144 -0
  19. package/blocks/_foundation/hooks/use-url-state.ts +74 -0
  20. package/blocks/appointments/README.md +21 -1
  21. package/blocks/appointments/block.json +39 -2
  22. package/blocks/appointments/components/event-type-related.tsx +75 -0
  23. package/blocks/appointments/components/meeting-list.tsx +8 -4
  24. package/blocks/appointments/components/meetings-screen.tsx +154 -0
  25. package/blocks/appointments/components/scheduling-screen.tsx +121 -0
  26. package/blocks/appointments/hooks/use-event-types.ts +17 -0
  27. package/blocks/block.types.ts +9 -0
  28. package/blocks/deals/README.md +34 -3
  29. package/blocks/deals/block.json +39 -4
  30. package/blocks/deals/components/deal-detail.tsx +4 -2
  31. package/blocks/deals/components/deal-related.tsx +87 -0
  32. package/blocks/deals/components/deals-screen.tsx +293 -0
  33. package/blocks/deals/components/pipeline-board.tsx +4 -2
  34. package/blocks/deals/hooks/use-default-owner.ts +60 -0
  35. package/blocks/deals/lib/deals-types.ts +14 -0
  36. package/blocks/forms/README.md +12 -0
  37. package/blocks/forms/block.json +9 -1
  38. package/blocks/forms/components/forms-screen.tsx +350 -0
  39. package/blocks/forms/components/submissions-table.tsx +13 -5
  40. package/blocks/lifecycle/README.md +16 -0
  41. package/blocks/lifecycle/block.json +23 -1
  42. package/blocks/lifecycle/components/account-related.tsx +66 -0
  43. package/blocks/lifecycle/components/lifecycle-screen.tsx +147 -0
  44. package/blocks/newsletter/README.md +13 -0
  45. package/blocks/newsletter/block.json +9 -1
  46. package/blocks/newsletter/components/newsletter-screen.tsx +402 -0
  47. package/blocks/newsletter/components/subscriber-table.tsx +12 -6
  48. package/blocks/nurture/README.md +12 -0
  49. package/blocks/nurture/block.json +9 -1
  50. package/blocks/nurture/components/nurture-screen.tsx +279 -0
  51. package/blocks/pages/README.md +12 -0
  52. package/blocks/pages/block.json +9 -1
  53. package/blocks/pages/components/content-library.tsx +37 -7
  54. package/blocks/pages/components/page-list.tsx +4 -2
  55. package/blocks/pages/components/pages-screen.tsx +154 -0
  56. package/blocks/quotes/README.md +17 -0
  57. package/blocks/quotes/block.json +26 -2
  58. package/blocks/quotes/components/quote-list.tsx +7 -3
  59. package/blocks/quotes/components/quote-related.tsx +120 -0
  60. package/blocks/quotes/components/quotes-screen.tsx +169 -0
  61. package/dist/cli.js +197 -37
  62. package/dist/index.browser.js +1 -1
  63. package/dist/index.browser.js.map +1 -1
  64. package/dist/index.browser.mjs +1 -1
  65. package/dist/index.browser.mjs.map +1 -1
  66. package/dist/index.js +1 -1
  67. package/dist/index.js.map +1 -1
  68. package/dist/index.mjs +1 -1
  69. package/dist/index.mjs.map +1 -1
  70. package/dist/react.browser.js +1 -1
  71. package/dist/react.browser.js.map +1 -1
  72. package/dist/react.browser.mjs +1 -1
  73. package/dist/react.browser.mjs.map +1 -1
  74. package/dist/react.js +1 -1
  75. package/dist/react.js.map +1 -1
  76. package/dist/react.mjs +1 -1
  77. package/dist/react.mjs.map +1 -1
  78. package/dist/types/blocks/create.d.ts +15 -1
  79. package/dist/types/blocks/doctor.d.ts +2 -1
  80. package/dist/types/blocks/flows.d.ts +20 -0
  81. package/dist/types/blocks/registry.d.ts +15 -0
  82. package/dist/types/version.d.ts +2 -2
  83. package/package.json +1 -1
  84. package/stacks/stack.schema.json +6 -1
  85. package/styles/styles.css +1 -1
  86. package/templates/next-app/README.md.tmpl +16 -0
  87. package/templates/next-app/app/globals.css.tmpl +105 -0
  88. package/templates/next-app/app/layout.tsx.tmpl +11 -2
  89. package/templates/next-app/components/app-shell.tsx.tmpl +238 -23
  90. package/templates/next-app/components/shell/connection-status.tsx.tmpl +109 -0
  91. package/templates/next-app/components/shell/nav-model.ts.tmpl +65 -0
  92. package/templates/next-app/components/shell/quick-jump.tsx.tmpl +131 -0
  93. package/templates/next-app/components/shell/theme-toggle.tsx.tmpl +40 -0
  94. package/templates/next-app/lib/nav.ts.tmpl +1 -1
@@ -0,0 +1,131 @@
1
+ "use client";
2
+
3
+ import { useEffect, useMemo, useRef, useState } from "react";
4
+ import { useRouter } from "next/navigation";
5
+ import { ArrowRight, Search } from "lucide-react";
6
+ import { Dialog, DialogContent, DialogDescription, DialogTitle, Kbd, cn } from "@graph8/sdk/ui";
7
+ import { groupNav, matchesNav, type NavEntry } from "./nav-model";
8
+
9
+ /**
10
+ * Quick jump (Cmd/Ctrl+K): type to filter the screens, arrows to move, Enter to go. No extra
11
+ * dependency: a Dialog, an input and a list. Add your own entries (records, actions) to `extra`.
12
+ */
13
+ export function QuickJump({ items, open, onOpenChange, extra = [] }: { items: readonly NavEntry[]; open: boolean; onOpenChange: (open: boolean) => void; extra?: readonly NavEntry[] }) {
14
+ const router = useRouter();
15
+ const [query, setQuery] = useState("");
16
+ const [cursor, setCursor] = useState(0);
17
+ const inputRef = useRef<HTMLInputElement>(null);
18
+ const listRef = useRef<HTMLUListElement>(null);
19
+
20
+ const results = useMemo(() => {
21
+ const all = [...items, ...extra].filter((i) => matchesNav(i, query));
22
+ return groupNav(all).flatMap((g) => g.items.map((item) => ({ item, group: g.label })));
23
+ }, [items, extra, query]);
24
+
25
+ useEffect(() => {
26
+ if (open) {
27
+ setQuery("");
28
+ setCursor(0);
29
+ }
30
+ }, [open]);
31
+
32
+ useEffect(() => {
33
+ setCursor((c) => Math.min(c, Math.max(results.length - 1, 0)));
34
+ }, [results.length]);
35
+
36
+ useEffect(() => {
37
+ const el = listRef.current?.querySelector<HTMLElement>(`[data-index="${cursor}"]`);
38
+ el?.scrollIntoView({ block: "nearest" });
39
+ }, [cursor]);
40
+
41
+ const go = (item: NavEntry | undefined) => {
42
+ if (!item) return;
43
+ onOpenChange(false);
44
+ router.push(item.path);
45
+ };
46
+
47
+ const listboxId = "g8-quick-jump-results";
48
+ const activeId = results[cursor] ? `g8-quick-jump-${cursor}` : undefined;
49
+
50
+ return (
51
+ <Dialog open={open} onOpenChange={onOpenChange}>
52
+ <DialogContent
53
+ showCloseButton={false}
54
+ className="top-[18%] max-w-lg translate-y-0 gap-0 overflow-hidden rounded-xl border border-border bg-popover p-0 shadow-xl sm:max-w-lg"
55
+ onOpenAutoFocus={(e) => {
56
+ e.preventDefault();
57
+ inputRef.current?.focus();
58
+ }}
59
+ >
60
+ <DialogTitle className="sr-only">Jump to a screen</DialogTitle>
61
+ <DialogDescription className="sr-only">Type to filter, use the arrow keys, press Enter to open.</DialogDescription>
62
+ <div className="flex items-center gap-2 border-b border-border px-3">
63
+ <Search className="size-4 shrink-0 text-text-color-muted" strokeWidth={1.5} aria-hidden />
64
+ <input
65
+ ref={inputRef}
66
+ role="combobox"
67
+ aria-expanded="true"
68
+ aria-controls={listboxId}
69
+ aria-activedescendant={activeId}
70
+ aria-label="Jump to a screen"
71
+ autoComplete="off"
72
+ spellCheck={false}
73
+ value={query}
74
+ placeholder="Jump to a screen"
75
+ onChange={(e) => {
76
+ setQuery(e.target.value);
77
+ setCursor(0);
78
+ }}
79
+ onKeyDown={(e) => {
80
+ if (e.key === "ArrowDown") {
81
+ e.preventDefault();
82
+ setCursor((c) => (results.length ? (c + 1) % results.length : 0));
83
+ } else if (e.key === "ArrowUp") {
84
+ e.preventDefault();
85
+ setCursor((c) => (results.length ? (c - 1 + results.length) % results.length : 0));
86
+ } else if (e.key === "Enter") {
87
+ e.preventDefault();
88
+ go(results[cursor]?.item);
89
+ }
90
+ }}
91
+ className="h-12 w-full min-w-0 bg-transparent text-sm text-foreground outline-none placeholder:text-text-color-muted"
92
+ />
93
+ <Kbd>esc</Kbd>
94
+ </div>
95
+ <ul ref={listRef} id={listboxId} role="listbox" aria-label="Screens" className="max-h-80 overflow-y-auto p-1.5">
96
+ {results.length === 0 ? (
97
+ <li className="px-3 py-8 text-center text-sm text-text-color-muted">No screen matches "{query}".</li>
98
+ ) : (
99
+ results.map(({ item, group }, index) => {
100
+ const Icon = item.icon;
101
+ const active = index === cursor;
102
+ return (
103
+ <li
104
+ key={`${index}:${item.path}`}
105
+ id={`g8-quick-jump-${index}`}
106
+ data-index={index}
107
+ role="option"
108
+ aria-selected={active}
109
+ onMouseEnter={() => setCursor(index)}
110
+ onMouseDown={(e) => e.preventDefault()}
111
+ onClick={() => go(item)}
112
+ className={cn("flex h-9 cursor-pointer items-center gap-2.5 rounded-md px-2.5 text-sm", active ? "bg-surface-selected text-foreground" : "text-foreground")}
113
+ >
114
+ <span className="flex size-5 shrink-0 items-center justify-center text-text-color-muted">
115
+ {Icon ? <Icon className="size-4" strokeWidth={1.5} aria-hidden /> : <span className="text-[10px] font-medium uppercase">{item.label.charAt(0)}</span>}
116
+ </span>
117
+ <span className="min-w-0 flex-1 truncate">
118
+ {item.label}
119
+ {item.description ? <span className="ml-2 text-xs text-text-color-muted">{item.description}</span> : null}
120
+ </span>
121
+ {group ? <span className="shrink-0 text-xs text-text-color-muted">{group}</span> : null}
122
+ {active ? <ArrowRight className="size-3.5 shrink-0 text-text-color-muted" strokeWidth={1.5} aria-hidden /> : null}
123
+ </li>
124
+ );
125
+ })
126
+ )}
127
+ </ul>
128
+ </DialogContent>
129
+ </Dialog>
130
+ );
131
+ }
@@ -0,0 +1,40 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState } from "react";
4
+ import { Moon, Sun } from "lucide-react";
5
+ import { cn } from "@graph8/sdk/ui";
6
+
7
+ const KEY = "g8.theme";
8
+
9
+ /** Light or dark, remembered in localStorage and applied as the `dark` class on <html> (see app/layout.tsx). */
10
+ export function ThemeToggle({ className }: { className?: string }) {
11
+ const [dark, setDark] = useState<boolean | null>(null);
12
+
13
+ useEffect(() => {
14
+ setDark(document.documentElement.classList.contains("dark"));
15
+ }, []);
16
+
17
+ const toggle = () => {
18
+ const next = !document.documentElement.classList.contains("dark");
19
+ document.documentElement.classList.toggle("dark", next);
20
+ try {
21
+ localStorage.setItem(KEY, next ? "dark" : "light");
22
+ } catch {
23
+ // Private mode: the choice lasts for this page only.
24
+ }
25
+ setDark(next);
26
+ };
27
+
28
+ const Icon = dark ? Sun : Moon;
29
+ return (
30
+ <button
31
+ type="button"
32
+ onClick={toggle}
33
+ aria-label={dark ? "Switch to light mode" : "Switch to dark mode"}
34
+ aria-pressed={dark ?? false}
35
+ className={cn("inline-flex size-8 items-center justify-center rounded-md text-text-color-muted transition-colors hover:bg-surface-hover hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", className)}
36
+ >
37
+ <Icon className="size-4" strokeWidth={1.5} aria-hidden />
38
+ </button>
39
+ );
40
+ }
@@ -1,2 +1,2 @@
1
1
  // Generated by `graph8-sdk create {{stackId}}`. One entry per screen; edit freely.
2
- export const nav: { label: string; path: string }[] = {{navJson}};
2
+ {{navImports}}export const nav: {{navType}} = {{navJson}};