@cueloop/client 0.1.0-alpha.11 → 0.1.0-alpha.12

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 (66) hide show
  1. package/package.json +7 -7
  2. package/src/App.autoclose.test.tsx +24 -35
  3. package/src/App.diff.test.tsx +5 -18
  4. package/src/App.export.test.tsx +8 -21
  5. package/src/App.plan-review.test.tsx +21 -52
  6. package/src/App.readonly.test.tsx +3 -18
  7. package/src/App.rendering.test.tsx +5 -13
  8. package/src/App.submit-confirm.test.tsx +7 -25
  9. package/src/App.test.tsx +13 -52
  10. package/src/App.tsx +197 -910
  11. package/src/components/AnnotationCard.stories.tsx +75 -0
  12. package/src/components/AnnotationCard.tsx +136 -0
  13. package/src/components/Breadcrumb.stories.tsx +19 -0
  14. package/src/components/Breadcrumb.tsx +38 -0
  15. package/src/components/CodeBlock.stories.tsx +25 -0
  16. package/src/components/CodeBlock.tsx +68 -0
  17. package/src/components/CompletionOverlay.stories.tsx +23 -0
  18. package/src/components/CompletionOverlay.tsx +50 -0
  19. package/src/components/ComposeBar.stories.tsx +18 -0
  20. package/src/components/ComposeBar.tsx +54 -0
  21. package/src/components/ConfirmCard.stories.tsx +22 -0
  22. package/src/components/ConfirmCard.tsx +130 -0
  23. package/src/components/DiffSheet.stories.tsx +44 -0
  24. package/src/components/DiffSheet.tsx +243 -0
  25. package/src/components/InboxList.stories.tsx +18 -0
  26. package/src/components/InboxList.tsx +42 -0
  27. package/src/components/PlanSheet.stories.tsx +81 -0
  28. package/src/components/PlanSheet.tsx +309 -0
  29. package/src/components/ReviewRail.stories.tsx +103 -0
  30. package/src/components/ReviewRail.tsx +158 -0
  31. package/src/components/SettingsDialog.stories.tsx +63 -0
  32. package/src/components/SettingsDialog.tsx +132 -0
  33. package/src/components/SettingsRows.stories.tsx +18 -0
  34. package/src/components/SettingsRows.tsx +92 -0
  35. package/src/components/__snapshots__/stories.test.tsx.snap +1189 -0
  36. package/src/components/format.ts +6 -0
  37. package/src/components/primitives/Button.stories.tsx +32 -0
  38. package/src/components/primitives/Button.tsx +44 -0
  39. package/src/components/primitives/Card.stories.tsx +25 -0
  40. package/src/components/primitives/Card.tsx +58 -0
  41. package/src/components/primitives/Dialog.stories.tsx +24 -0
  42. package/src/components/primitives/Dialog.tsx +52 -0
  43. package/src/components/primitives/StatusBar.stories.tsx +11 -0
  44. package/src/components/primitives/StatusBar.tsx +22 -0
  45. package/src/components/primitives/Tabs.stories.tsx +29 -0
  46. package/src/components/primitives/Tabs.tsx +97 -0
  47. package/src/components/primitives/Toolbar.stories.tsx +17 -0
  48. package/src/components/primitives/Toolbar.tsx +14 -0
  49. package/src/components/stories-app.tsx +94 -0
  50. package/src/components/stories.test.tsx +51 -0
  51. package/src/components/story-fixtures.ts +90 -0
  52. package/src/components/story.ts +66 -0
  53. package/src/components/syntax-highlight.ts +57 -0
  54. package/src/components/theme-context.stories.tsx +41 -0
  55. package/src/components/theme-context.tsx +30 -0
  56. package/src/key-bindings.test.ts +103 -0
  57. package/src/key-bindings.ts +332 -0
  58. package/src/keymap.ts +8 -2
  59. package/src/run.ts +3 -1
  60. package/src/session-controller.ts +28 -7
  61. package/src/test-support.ts +87 -0
  62. package/src/theme.ts +3 -0
  63. package/src/view.test.ts +35 -13
  64. package/src/view.ts +41 -64
  65. package/src/syntax.test.ts +0 -41
  66. package/src/syntax.ts +0 -88
@@ -0,0 +1,132 @@
1
+ /**
2
+ * The settings overlay, promoted from the design prototype: a centered
3
+ * Dialog with a left category nav and a right body of typed rows, all driven
4
+ * by one settings record. The component is controlled - the caller owns the
5
+ * open flag, the active category/row/zone, and value changes - so the app's
6
+ * keyboard grammar (and the stories catalog) can drive it without a second
7
+ * key handler inside. The modal sizes itself from the terminal dimensions.
8
+ */
9
+
10
+ import React from "react";
11
+ import { useTerminalDimensions } from "@opentui/react";
12
+ import type { Theme } from "../theme";
13
+ import { useComponentTheme } from "./theme-context";
14
+ import { Dialog } from "./primitives/Dialog";
15
+ import { CycleRow, TextRow, ToggleRow, type SettingsRowDescriptor } from "./SettingsRows";
16
+
17
+ export interface SettingsCategory {
18
+ id: string;
19
+ name: string;
20
+ description: string;
21
+ rows: SettingsRowDescriptor[];
22
+ }
23
+
24
+ export type SettingsValues = Record<string, string | boolean>;
25
+
26
+ export interface SettingsDialogProps {
27
+ isOpen: boolean;
28
+ categories: SettingsCategory[];
29
+ values: SettingsValues;
30
+ activeCategoryId: string;
31
+ activeRowIndex: number;
32
+ activeZone: "nav" | "body";
33
+ onCategorySelect: (categoryId: string) => void;
34
+ onRowActivate: (row: SettingsRowDescriptor) => void;
35
+ theme?: Theme;
36
+ }
37
+
38
+ export function SettingsDialog({
39
+ isOpen,
40
+ categories,
41
+ values,
42
+ activeCategoryId,
43
+ activeRowIndex,
44
+ activeZone,
45
+ onCategorySelect,
46
+ onRowActivate,
47
+ theme,
48
+ }: SettingsDialogProps): React.ReactNode {
49
+ const tokens = useComponentTheme(theme);
50
+ const { width: terminalWidth, height: terminalHeight } = useTerminalDimensions();
51
+ if (!isOpen) return null;
52
+ const category = categories.find((candidate) => candidate.id === activeCategoryId) ?? categories[0];
53
+ if (!category) return null;
54
+ return (
55
+ <Dialog
56
+ isOpen
57
+ title=" Settings "
58
+ width={Math.min(76, terminalWidth - 6)}
59
+ height={Math.min(22, terminalHeight - 4)}
60
+ theme={theme}
61
+ >
62
+ <box style={{ flexDirection: "row", flexGrow: 1 }}>
63
+ <box style={{ flexDirection: "column", width: 18, paddingLeft: 1, paddingRight: 1 }}>
64
+ <text fg={tokens.textDim}>SETTINGS</text>
65
+ {categories.map((candidate) => {
66
+ const isActive = candidate.id === category.id;
67
+ return (
68
+ <box
69
+ key={candidate.id}
70
+ style={{ backgroundColor: isActive && activeZone === "nav" ? tokens.elevated : undefined }}
71
+ onMouseUp={() => onCategorySelect(candidate.id)}
72
+ >
73
+ <text fg={isActive ? tokens.accent : tokens.textMuted}>{`${isActive ? "› " : " "}${candidate.name}`}</text>
74
+ </box>
75
+ );
76
+ })}
77
+ </box>
78
+ <box style={{ width: 1, backgroundColor: tokens.border }} />
79
+ <box style={{ flexDirection: "column", flexGrow: 1, paddingLeft: 2, paddingRight: 1 }}>
80
+ <text fg={tokens.text}>{category.name}</text>
81
+ <text fg={tokens.textDim}>{category.description}</text>
82
+ <box style={{ height: 1 }} />
83
+ {category.rows.map((row, rowIndex) => {
84
+ const isActive = activeZone === "body" && rowIndex === activeRowIndex;
85
+ const value = values[row.key];
86
+ if (row.kind === "toggle") {
87
+ return (
88
+ <ToggleRow
89
+ key={row.key}
90
+ label={row.label}
91
+ value={value === true}
92
+ isActive={isActive}
93
+ onPress={() => onRowActivate(row)}
94
+ theme={theme}
95
+ />
96
+ );
97
+ }
98
+ if (row.kind === "cycle") {
99
+ return (
100
+ <CycleRow
101
+ key={row.key}
102
+ label={row.label}
103
+ value={String(value ?? "")}
104
+ isActive={isActive}
105
+ onPress={() => onRowActivate(row)}
106
+ theme={theme}
107
+ />
108
+ );
109
+ }
110
+ return (
111
+ <TextRow
112
+ key={row.key}
113
+ label={row.label}
114
+ value={String(value ?? "")}
115
+ isActive={isActive}
116
+ onPress={() => onRowActivate(row)}
117
+ theme={theme}
118
+ />
119
+ );
120
+ })}
121
+ </box>
122
+ </box>
123
+ <box style={{ flexDirection: "row", height: 1, backgroundColor: tokens.elevated, paddingLeft: 1 }}>
124
+ <text fg={tokens.textDim}>
125
+ {activeZone === "nav"
126
+ ? "j/k category · l/⏎ into settings · esc close"
127
+ : "j/k row · l/space change · h back · esc close"}
128
+ </text>
129
+ </box>
130
+ </Dialog>
131
+ );
132
+ }
@@ -0,0 +1,18 @@
1
+ import React from "react";
2
+ import { DARK } from "../theme";
3
+ import type { Story, StoryMeta } from "./story";
4
+ import { CycleRow, TextRow, ToggleRow } from "./SettingsRows";
5
+
6
+ export const meta: StoryMeta = { title: "SettingsRows" };
7
+
8
+ export const AllKinds: Story = {
9
+ render: () => (
10
+ <box style={{ flexDirection: "column", width: 50 }}>
11
+ <ToggleRow label="Line numbers" value={true} isActive={false} onPress={() => {}} />
12
+ <ToggleRow label="Hide whitespace" value={false} isActive={true} onPress={() => {}} />
13
+ <CycleRow label="Palette" value="dark" isActive={false} onPress={() => {}} />
14
+ <TextRow label="Vault path" value="~/vaults/notes" isActive={false} onPress={() => {}} />
15
+ </box>
16
+ ),
17
+ expectedColors: [DARK.green, DARK.accent, DARK.elevated],
18
+ };
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Typed settings rows for the settings dialog: toggle (on/off), cycle
3
+ * (enumerated options), and text (freeform value). Every row is one line -
4
+ * label column left, value right - and presses go through `onPress` so mouse
5
+ * and keyboard activation share one path.
6
+ */
7
+
8
+ import React from "react";
9
+ import type { Theme } from "../theme";
10
+ import { useComponentTheme } from "./theme-context";
11
+
12
+ export interface SettingsRowDescriptor {
13
+ key: string;
14
+ label: string;
15
+ kind: "toggle" | "cycle" | "text";
16
+ options?: string[];
17
+ hint?: string;
18
+ }
19
+
20
+ interface RowShellProps {
21
+ label: string;
22
+ isActive: boolean;
23
+ onPress: () => void;
24
+ theme?: Theme;
25
+ children: React.ReactNode;
26
+ }
27
+
28
+ function RowShell({ label, isActive, onPress, theme, children }: RowShellProps): React.ReactNode {
29
+ const tokens = useComponentTheme(theme);
30
+ return (
31
+ <box
32
+ style={{ flexDirection: "row", height: 1, backgroundColor: isActive ? tokens.elevated : undefined }}
33
+ onMouseUp={onPress}
34
+ >
35
+ <box style={{ width: 26, paddingLeft: 1 }}>
36
+ <text fg={isActive ? tokens.text : tokens.textMuted}>{label}</text>
37
+ </box>
38
+ {children}
39
+ </box>
40
+ );
41
+ }
42
+
43
+ export interface ToggleRowProps {
44
+ label: string;
45
+ value: boolean;
46
+ isActive: boolean;
47
+ onPress: () => void;
48
+ theme?: Theme;
49
+ }
50
+
51
+ export function ToggleRow({ label, value, isActive, onPress, theme }: ToggleRowProps): React.ReactNode {
52
+ const tokens = useComponentTheme(theme);
53
+ return (
54
+ <RowShell label={label} isActive={isActive} onPress={onPress} theme={theme}>
55
+ <text fg={value ? tokens.green : tokens.accent}>{value ? "on" : "off"}</text>
56
+ </RowShell>
57
+ );
58
+ }
59
+
60
+ export interface CycleRowProps {
61
+ label: string;
62
+ value: string;
63
+ isActive: boolean;
64
+ onPress: () => void;
65
+ theme?: Theme;
66
+ }
67
+
68
+ export function CycleRow({ label, value, isActive, onPress, theme }: CycleRowProps): React.ReactNode {
69
+ const tokens = useComponentTheme(theme);
70
+ return (
71
+ <RowShell label={label} isActive={isActive} onPress={onPress} theme={theme}>
72
+ <text fg={tokens.accent}>{value}</text>
73
+ </RowShell>
74
+ );
75
+ }
76
+
77
+ export interface TextRowProps {
78
+ label: string;
79
+ value: string;
80
+ isActive: boolean;
81
+ onPress: () => void;
82
+ theme?: Theme;
83
+ }
84
+
85
+ export function TextRow({ label, value, isActive, onPress, theme }: TextRowProps): React.ReactNode {
86
+ const tokens = useComponentTheme(theme);
87
+ return (
88
+ <RowShell label={label} isActive={isActive} onPress={onPress} theme={theme}>
89
+ <text fg={tokens.accent}>{value}</text>
90
+ </RowShell>
91
+ );
92
+ }