flowbook 0.2.15 → 0.2.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowbook",
3
- "version": "0.2.15",
3
+ "version": "0.2.16",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "flowbook": "dist/cli.js"
@@ -6,7 +6,7 @@ import { EmptyState } from "./components/EmptyState";
6
6
 
7
7
  export function App() {
8
8
  const [selectedId, setSelectedId] = useState<string | null>(null);
9
- const [searchQuery] = useState("");
9
+ const [searchQuery, setSearchQuery] = useState("");
10
10
 
11
11
  const filteredFlows = useMemo(() => {
12
12
  if (!searchQuery) return data.flows;
@@ -38,14 +38,18 @@ export function App() {
38
38
  <span className="ml-4 text-sm font-mono" style={{ color: 'var(--fb-text-muted)' }}>
39
39
  flowbook — localhost:6200
40
40
  </span>
41
+ <span className="ml-auto text-sm font-mono" style={{ color: 'var(--fb-text-muted)' }}>
42
+ {data.flows.length} flows
43
+ </span>
41
44
  </div>
42
-
43
45
  {/* Content area */}
44
46
  <div className="flex" style={{ height: '520px' }}>
45
47
  <Sidebar
46
48
  flows={filteredFlows}
47
49
  selectedId={selectedId}
48
50
  onSelect={setSelectedId}
51
+ searchQuery={searchQuery}
52
+ onSearchChange={setSearchQuery}
49
53
  />
50
54
  <main className="flex-1 overflow-auto" style={{ background: 'var(--fb-bg-content)' }}>
51
55
  {selectedFlow ? (
@@ -8,10 +8,22 @@ interface FlowViewProps {
8
8
  export function FlowView({ flow }: FlowViewProps) {
9
9
  return (
10
10
  <div className="p-8 max-w-5xl mx-auto">
11
- {/* Title */}
12
- <h1 className="text-2xl font-bold mb-1" style={{ color: 'var(--fb-text-primary)' }}>
13
- {flow.title}
14
- </h1>
11
+ {/* Title + Category badge */}
12
+ <div className="flex items-center gap-3 mb-1">
13
+ <h1 className="text-2xl font-bold" style={{ color: 'var(--fb-text-primary)' }}>
14
+ {flow.title}
15
+ </h1>
16
+ <span
17
+ className="px-2.5 py-0.5 rounded-md text-xs font-medium"
18
+ style={{
19
+ background: 'rgba(99, 102, 241, 0.12)',
20
+ color: '#818cf8',
21
+ border: '1px solid rgba(99, 102, 241, 0.2)',
22
+ }}
23
+ >
24
+ {flow.category}
25
+ </span>
26
+ </div>
15
27
 
16
28
  {/* Description */}
17
29
  {flow.description && (
@@ -1,13 +1,15 @@
1
- import { useMemo } from "react";
1
+ import { useMemo, useState } from "react";
2
2
  import type { FlowEntry } from "../../types";
3
3
 
4
4
  interface SidebarProps {
5
5
  flows: FlowEntry[];
6
6
  selectedId: string | null;
7
7
  onSelect: (id: string) => void;
8
+ searchQuery: string;
9
+ onSearchChange: (query: string) => void;
8
10
  }
9
11
 
10
- export function Sidebar({ flows, selectedId, onSelect }: SidebarProps) {
12
+ export function Sidebar({ flows, selectedId, onSelect, searchQuery, onSearchChange }: SidebarProps) {
11
13
  const categories = useMemo(() => {
12
14
  const map = new Map<string, FlowEntry[]>();
13
15
  for (const flow of flows) {
@@ -21,45 +23,107 @@ export function Sidebar({ flows, selectedId, onSelect }: SidebarProps) {
21
23
  return Array.from(map.entries()).sort(([a], [b]) => a.localeCompare(b));
22
24
  }, [flows]);
23
25
 
26
+ const [collapsed, setCollapsed] = useState<Set<string>>(new Set());
27
+
28
+ function toggleCategory(category: string) {
29
+ setCollapsed((prev) => {
30
+ const next = new Set(prev);
31
+ if (next.has(category)) next.delete(category);
32
+ else next.add(category);
33
+ return next;
34
+ });
35
+ }
36
+
24
37
  return (
25
38
  <aside
26
- className="w-64 overflow-y-auto shrink-0 border-r"
39
+ className="w-64 overflow-y-auto shrink-0 border-r flex flex-col"
27
40
  style={{
28
41
  background: 'var(--fb-bg-sidebar)',
29
42
  borderColor: 'var(--fb-border)',
30
43
  }}
31
44
  >
32
- <nav className="p-5 space-y-4">
33
- {categories.map(([category, items]) => (
34
- <div key={category}>
35
- <h3
36
- className="text-xs font-bold uppercase tracking-widest mb-2 px-2"
37
- style={{ color: 'var(--fb-text-muted)' }}
38
- >
39
- {category}
40
- </h3>
41
- <div className="space-y-0.5">
42
- {items.map((flow) => {
43
- const isActive = selectedId === flow.id;
44
- return (
45
- <button
46
- key={flow.id}
47
- onClick={() => onSelect(flow.id)}
48
- className={`w-full text-left px-4 py-2 rounded-lg text-sm transition-colors ${
49
- isActive ? 'font-medium' : 'hover:bg-white/5'
50
- }`}
51
- style={{
52
- color: isActive ? 'var(--fb-accent-green)' : 'var(--fb-text-secondary)',
53
- background: isActive ? 'rgba(74, 222, 128, 0.08)' : undefined,
54
- }}
55
- >
56
- {flow.title}
57
- </button>
58
- );
59
- })}
45
+ {/* Search */}
46
+ <div className="p-3 border-b" style={{ borderColor: 'var(--fb-border)' }}>
47
+ <div className="relative">
48
+ <svg
49
+ className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4"
50
+ style={{ color: 'var(--fb-text-muted)' }}
51
+ fill="none"
52
+ viewBox="0 0 24 24"
53
+ stroke="currentColor"
54
+ strokeWidth={2}
55
+ >
56
+ <circle cx="11" cy="11" r="8" />
57
+ <path d="m21 21-4.35-4.35" />
58
+ </svg>
59
+ <input
60
+ type="text"
61
+ placeholder="Search flows..."
62
+ value={searchQuery}
63
+ onChange={(e) => onSearchChange(e.target.value)}
64
+ className="w-full pl-9 pr-3 py-2 rounded-lg text-sm outline-none"
65
+ style={{
66
+ background: 'rgba(255,255,255,0.05)',
67
+ color: 'var(--fb-text-primary)',
68
+ border: '1px solid var(--fb-border)',
69
+ }}
70
+ />
71
+ </div>
72
+ </div>
73
+
74
+ {/* Categories */}
75
+ <nav className="p-3 space-y-1 flex-1 overflow-y-auto">
76
+ {categories.map(([category, items]) => {
77
+ const isCollapsed = collapsed.has(category);
78
+ return (
79
+ <div key={category}>
80
+ <button
81
+ onClick={() => toggleCategory(category)}
82
+ className="w-full flex items-center gap-1.5 px-2 py-2 rounded-lg text-xs font-bold uppercase tracking-widest hover:bg-white/5 transition-colors"
83
+ style={{ color: 'var(--fb-text-muted)' }}
84
+ >
85
+ <svg
86
+ className="w-3 h-3 transition-transform"
87
+ style={{ transform: isCollapsed ? 'rotate(-90deg)' : 'rotate(0deg)' }}
88
+ fill="currentColor"
89
+ viewBox="0 0 20 20"
90
+ >
91
+ <path fillRule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clipRule="evenodd" />
92
+ </svg>
93
+ <span className="flex-1 text-left">{category}</span>
94
+ <span
95
+ className="text-xs font-medium tabular-nums"
96
+ style={{ color: 'var(--fb-text-muted)', opacity: 0.7 }}
97
+ >
98
+ {items.length}
99
+ </span>
100
+ </button>
101
+ {!isCollapsed && (
102
+ <div className="space-y-0.5 mt-0.5">
103
+ {items.map((flow) => {
104
+ const isActive = selectedId === flow.id;
105
+ return (
106
+ <button
107
+ key={flow.id}
108
+ onClick={() => onSelect(flow.id)}
109
+ className={`w-full text-left px-4 py-2 rounded-lg text-sm transition-colors ${
110
+ isActive ? 'font-medium' : 'hover:bg-white/5'
111
+ }`}
112
+ style={{
113
+ color: isActive ? 'var(--fb-accent-green)' : 'var(--fb-text-secondary)',
114
+ background: isActive ? 'rgba(74, 222, 128, 0.08)' : undefined,
115
+ paddingLeft: '1.75rem',
116
+ }}
117
+ >
118
+ {flow.title}
119
+ </button>
120
+ );
121
+ })}
122
+ </div>
123
+ )}
60
124
  </div>
61
- </div>
62
- ))}
125
+ );
126
+ })}
63
127
  {categories.length === 0 && (
64
128
  <p className="text-sm px-2 py-4 text-center" style={{ color: 'var(--fb-text-muted)' }}>
65
129
  No flows found