epoch-tui 0.1.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.
- package/dist/App.d.ts +3 -0
- package/dist/App.js +38 -0
- package/dist/components/calendar/CalendarPane.d.ts +2 -0
- package/dist/components/calendar/CalendarPane.js +91 -0
- package/dist/components/calendar/DayCell.d.ts +7 -0
- package/dist/components/calendar/DayCell.js +26 -0
- package/dist/components/calendar/MonthView.d.ts +7 -0
- package/dist/components/calendar/MonthView.js +10 -0
- package/dist/components/common/BorderedBox.d.ts +18 -0
- package/dist/components/common/BorderedBox.js +29 -0
- package/dist/components/common/ClearTimelineDialog.d.ts +2 -0
- package/dist/components/common/ClearTimelineDialog.js +33 -0
- package/dist/components/common/FullscreenBackground.d.ts +11 -0
- package/dist/components/common/FullscreenBackground.js +12 -0
- package/dist/components/common/HelpDialog.d.ts +2 -0
- package/dist/components/common/HelpDialog.js +40 -0
- package/dist/components/common/Modal.d.ts +10 -0
- package/dist/components/common/Modal.js +15 -0
- package/dist/components/common/Separator.d.ts +8 -0
- package/dist/components/common/Separator.js +10 -0
- package/dist/components/common/ThemeDialog.d.ts +2 -0
- package/dist/components/common/ThemeDialog.js +117 -0
- package/dist/components/common/ThemedScreen.d.ts +22 -0
- package/dist/components/common/ThemedScreen.js +36 -0
- package/dist/components/common/ThemedText.d.ts +11 -0
- package/dist/components/common/ThemedText.js +20 -0
- package/dist/components/layout/Pane.d.ts +12 -0
- package/dist/components/layout/Pane.js +10 -0
- package/dist/components/layout/ThreeColumnLayout.d.ts +13 -0
- package/dist/components/layout/ThreeColumnLayout.js +22 -0
- package/dist/components/overview/OverviewScreen.d.ts +2 -0
- package/dist/components/overview/OverviewScreen.js +138 -0
- package/dist/components/tasks/TaskHeader.d.ts +7 -0
- package/dist/components/tasks/TaskHeader.js +8 -0
- package/dist/components/tasks/TaskItem.d.ts +10 -0
- package/dist/components/tasks/TaskItem.js +25 -0
- package/dist/components/tasks/TaskList.d.ts +11 -0
- package/dist/components/tasks/TaskList.js +11 -0
- package/dist/components/tasks/TasksPane.d.ts +2 -0
- package/dist/components/tasks/TasksPane.js +410 -0
- package/dist/components/timeline/TimelineEntry.d.ts +9 -0
- package/dist/components/timeline/TimelineEntry.js +26 -0
- package/dist/components/timeline/TimelinePane.d.ts +2 -0
- package/dist/components/timeline/TimelinePane.js +78 -0
- package/dist/contexts/AppContext.d.ts +47 -0
- package/dist/contexts/AppContext.js +104 -0
- package/dist/contexts/StorageContext.d.ts +15 -0
- package/dist/contexts/StorageContext.js +83 -0
- package/dist/contexts/ThemeContext.d.ts +15 -0
- package/dist/contexts/ThemeContext.js +44 -0
- package/dist/hooks/useKeyboardNav.d.ts +1 -0
- package/dist/hooks/useKeyboardNav.js +89 -0
- package/dist/hooks/useTerminalSize.d.ts +9 -0
- package/dist/hooks/useTerminalSize.js +34 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +8 -0
- package/dist/services/calendarService.d.ts +18 -0
- package/dist/services/calendarService.js +57 -0
- package/dist/services/storage.d.ts +14 -0
- package/dist/services/storage.js +130 -0
- package/dist/services/taskService.d.ts +17 -0
- package/dist/services/taskService.js +106 -0
- package/dist/services/timelineService.d.ts +25 -0
- package/dist/services/timelineService.js +78 -0
- package/dist/themes/amazon.d.ts +2 -0
- package/dist/themes/amazon.js +37 -0
- package/dist/themes/amazonLight.d.ts +2 -0
- package/dist/themes/amazonLight.js +38 -0
- package/dist/themes/apple.d.ts +2 -0
- package/dist/themes/apple.js +38 -0
- package/dist/themes/appleLight.d.ts +2 -0
- package/dist/themes/appleLight.js +38 -0
- package/dist/themes/atomOneDark.d.ts +2 -0
- package/dist/themes/atomOneDark.js +37 -0
- package/dist/themes/atomOneLight.d.ts +2 -0
- package/dist/themes/atomOneLight.js +38 -0
- package/dist/themes/batman.d.ts +2 -0
- package/dist/themes/batman.js +37 -0
- package/dist/themes/catppuccin.d.ts +2 -0
- package/dist/themes/catppuccin.js +37 -0
- package/dist/themes/catppuccinLatte.d.ts +2 -0
- package/dist/themes/catppuccinLatte.js +38 -0
- package/dist/themes/claude.d.ts +2 -0
- package/dist/themes/claude.js +48 -0
- package/dist/themes/claudeCode.d.ts +2 -0
- package/dist/themes/claudeCode.js +47 -0
- package/dist/themes/cursor.d.ts +2 -0
- package/dist/themes/cursor.js +38 -0
- package/dist/themes/cursorLight.d.ts +2 -0
- package/dist/themes/cursorLight.js +38 -0
- package/dist/themes/dark.d.ts +2 -0
- package/dist/themes/dark.js +38 -0
- package/dist/themes/githubDark.d.ts +2 -0
- package/dist/themes/githubDark.js +37 -0
- package/dist/themes/githubLight.d.ts +2 -0
- package/dist/themes/githubLight.js +38 -0
- package/dist/themes/index.d.ts +9 -0
- package/dist/themes/index.js +83 -0
- package/dist/themes/instagram.d.ts +2 -0
- package/dist/themes/instagram.js +37 -0
- package/dist/themes/instagramLight.d.ts +2 -0
- package/dist/themes/instagramLight.js +38 -0
- package/dist/themes/intellij.d.ts +2 -0
- package/dist/themes/intellij.js +37 -0
- package/dist/themes/intellijLight.d.ts +2 -0
- package/dist/themes/intellijLight.js +38 -0
- package/dist/themes/light.d.ts +2 -0
- package/dist/themes/light.js +38 -0
- package/dist/themes/nord.d.ts +2 -0
- package/dist/themes/nord.js +37 -0
- package/dist/themes/nordLight.d.ts +2 -0
- package/dist/themes/nordLight.js +38 -0
- package/dist/themes/postman.d.ts +2 -0
- package/dist/themes/postman.js +37 -0
- package/dist/themes/postmanLight.d.ts +2 -0
- package/dist/themes/postmanLight.js +38 -0
- package/dist/themes/spiderman.d.ts +2 -0
- package/dist/themes/spiderman.js +37 -0
- package/dist/themes/terminal.d.ts +2 -0
- package/dist/themes/terminal.js +37 -0
- package/dist/themes/ubuntu.d.ts +2 -0
- package/dist/themes/ubuntu.js +37 -0
- package/dist/themes/ubuntuLight.d.ts +2 -0
- package/dist/themes/ubuntuLight.js +38 -0
- package/dist/themes/x.d.ts +2 -0
- package/dist/themes/x.js +38 -0
- package/dist/themes/xLight.d.ts +2 -0
- package/dist/themes/xLight.js +38 -0
- package/dist/types/calendar.d.ts +19 -0
- package/dist/types/calendar.js +1 -0
- package/dist/types/storage.d.ts +21 -0
- package/dist/types/storage.js +1 -0
- package/dist/types/task.d.ts +21 -0
- package/dist/types/task.js +1 -0
- package/dist/types/theme.d.ts +38 -0
- package/dist/types/theme.js +1 -0
- package/dist/types/timeline.d.ts +23 -0
- package/dist/types/timeline.js +9 -0
- package/dist/utils/date.d.ts +7 -0
- package/dist/utils/date.js +37 -0
- package/dist/utils/logger.d.ts +3 -0
- package/dist/utils/logger.js +39 -0
- package/dist/utils/tree.d.ts +11 -0
- package/dist/utils/tree.js +64 -0
- package/dist/utils/validation.d.ts +7 -0
- package/dist/utils/validation.js +35 -0
- package/package.json +44 -0
package/dist/App.d.ts
ADDED
package/dist/App.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import { ThemeProvider, useTheme } from "./contexts/ThemeContext";
|
|
4
|
+
import { StorageProvider } from "./contexts/StorageContext";
|
|
5
|
+
import { AppProvider, useApp } from "./contexts/AppContext";
|
|
6
|
+
import { useKeyboardNav } from "./hooks/useKeyboardNav";
|
|
7
|
+
import { useTerminalSize } from "./hooks/useTerminalSize";
|
|
8
|
+
import { ThreeColumnLayout } from "./components/layout/ThreeColumnLayout";
|
|
9
|
+
import { CalendarPane } from "./components/calendar/CalendarPane";
|
|
10
|
+
import { TasksPane } from "./components/tasks/TasksPane";
|
|
11
|
+
import { TimelinePane } from "./components/timeline/TimelinePane";
|
|
12
|
+
import { HelpDialog } from "./components/common/HelpDialog";
|
|
13
|
+
import { ThemeDialog } from "./components/common/ThemeDialog";
|
|
14
|
+
import { ClearTimelineDialog } from "./components/common/ClearTimelineDialog";
|
|
15
|
+
import { OverviewScreen } from "./components/overview/OverviewScreen";
|
|
16
|
+
import { FullscreenBackground } from "./components/common/FullscreenBackground";
|
|
17
|
+
const AppContent = () => {
|
|
18
|
+
const { showHelp, showOverview, exitConfirmation, activePane, showThemeDialog, showClearTimelineDialog, } = useApp();
|
|
19
|
+
const { theme } = useTheme();
|
|
20
|
+
useKeyboardNav();
|
|
21
|
+
/* Use available width and height - automatically updates on resize */
|
|
22
|
+
const { width, height } = useTerminalSize();
|
|
23
|
+
// When a modal dialog is open, render only the modal
|
|
24
|
+
if (showThemeDialog) {
|
|
25
|
+
return _jsx(ThemeDialog, {});
|
|
26
|
+
}
|
|
27
|
+
if (showHelp) {
|
|
28
|
+
return _jsx(HelpDialog, {});
|
|
29
|
+
}
|
|
30
|
+
if (showClearTimelineDialog) {
|
|
31
|
+
return _jsx(ClearTimelineDialog, {});
|
|
32
|
+
}
|
|
33
|
+
return (_jsx(FullscreenBackground, { backgroundColor: theme.colors.background || "black", children: _jsxs(Box, { flexDirection: "column", width: width, height: height, padding: 1, backgroundColor: theme.colors.background, children: [showOverview ? (_jsx(OverviewScreen, {})) : (_jsx(ThreeColumnLayout, { leftPane: _jsx(CalendarPane, {}), centerPane: _jsx(TasksPane, {}), rightPane: _jsx(TimelinePane, {}), activePane: activePane, height: height - 2 })), exitConfirmation && (_jsx(Box, { width: "100%", justifyContent: "center", paddingY: 1, children: _jsxs(Text, { backgroundColor: "red", color: "white", bold: true, children: [" ", "Press Ctrl+C again to exit Epoch", " "] }) }))] }) }));
|
|
34
|
+
};
|
|
35
|
+
const App = () => {
|
|
36
|
+
return (_jsx(StorageProvider, { children: _jsx(ThemeProvider, { initialTheme: "dark", children: _jsx(AppProvider, { children: _jsx(AppContent, {}) }) }) }));
|
|
37
|
+
};
|
|
38
|
+
export default App;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect } from "react";
|
|
3
|
+
import { Box, Text, useInput } from "ink";
|
|
4
|
+
import { addDays, addWeeks, subDays, subWeeks } from "date-fns";
|
|
5
|
+
import { useTheme } from "../../contexts/ThemeContext";
|
|
6
|
+
import { useApp } from "../../contexts/AppContext";
|
|
7
|
+
import { Pane } from "../layout/Pane";
|
|
8
|
+
import { MonthView } from "./MonthView";
|
|
9
|
+
import { calendarService } from "../../services/calendarService";
|
|
10
|
+
export const CalendarPane = () => {
|
|
11
|
+
const { theme } = useTheme();
|
|
12
|
+
const { selectedDate, setSelectedDate, tasks, activePane, isModalOpen, isInputMode } = useApp();
|
|
13
|
+
const [currentMonth, setCurrentMonth] = useState({
|
|
14
|
+
year: selectedDate.year,
|
|
15
|
+
month: selectedDate.month,
|
|
16
|
+
});
|
|
17
|
+
// Update currentMonth when selectedDate changes to different month
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
if (selectedDate.month !== currentMonth.month ||
|
|
20
|
+
selectedDate.year !== currentMonth.year) {
|
|
21
|
+
setCurrentMonth({ year: selectedDate.year, month: selectedDate.month });
|
|
22
|
+
}
|
|
23
|
+
}, [selectedDate.year, selectedDate.month]);
|
|
24
|
+
const calendarView = calendarService.generateMonthView(currentMonth.year, currentMonth.month, selectedDate, tasks);
|
|
25
|
+
const isFocused = activePane === "calendar" && !isModalOpen;
|
|
26
|
+
const navigateDate = (newDate) => {
|
|
27
|
+
setSelectedDate({
|
|
28
|
+
year: newDate.getFullYear(),
|
|
29
|
+
month: newDate.getMonth(),
|
|
30
|
+
day: newDate.getDate(),
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
useInput((input, key) => {
|
|
34
|
+
if (!isFocused)
|
|
35
|
+
return;
|
|
36
|
+
const currentDate = new Date(selectedDate.year, selectedDate.month, selectedDate.day);
|
|
37
|
+
// Navigate by day (h/l or left/right)
|
|
38
|
+
if (input === "h" || key.leftArrow) {
|
|
39
|
+
navigateDate(subDays(currentDate, 1));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (input === "l" || key.rightArrow) {
|
|
43
|
+
navigateDate(addDays(currentDate, 1));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
// Navigate by week (j/k or down/up)
|
|
47
|
+
if (input === "j" || key.downArrow) {
|
|
48
|
+
navigateDate(addWeeks(currentDate, 1));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (input === "k" || key.upArrow) {
|
|
52
|
+
navigateDate(subWeeks(currentDate, 1));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// Navigate by month
|
|
56
|
+
if (input === "n") {
|
|
57
|
+
const next = calendarService.getNextMonth(currentMonth.year, currentMonth.month);
|
|
58
|
+
setCurrentMonth(next);
|
|
59
|
+
// Move selected date to first day of new month
|
|
60
|
+
setSelectedDate({
|
|
61
|
+
year: next.year,
|
|
62
|
+
month: next.month,
|
|
63
|
+
day: 1,
|
|
64
|
+
});
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (input === "p") {
|
|
68
|
+
const prev = calendarService.getPreviousMonth(currentMonth.year, currentMonth.month);
|
|
69
|
+
setCurrentMonth(prev);
|
|
70
|
+
// Move selected date to first day of new month
|
|
71
|
+
setSelectedDate({
|
|
72
|
+
year: prev.year,
|
|
73
|
+
month: prev.month,
|
|
74
|
+
day: 1,
|
|
75
|
+
});
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
// Go to today
|
|
79
|
+
if (input === "T") {
|
|
80
|
+
const today = new Date();
|
|
81
|
+
setSelectedDate({
|
|
82
|
+
year: today.getFullYear(),
|
|
83
|
+
month: today.getMonth(),
|
|
84
|
+
day: today.getDate(),
|
|
85
|
+
});
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
}, { isActive: isFocused && !isInputMode });
|
|
89
|
+
const monthName = calendarService.getMonthName(currentMonth.month);
|
|
90
|
+
return (_jsx(Pane, { title: `${monthName} ${currentMonth.year}`, isFocused: isFocused, center: true, children: _jsxs(Box, { flexDirection: "column", alignItems: "center", children: [_jsx(MonthView, { calendarView: calendarView }), _jsxs(Box, { marginTop: 1, flexDirection: "column", alignItems: "center", children: [_jsx(Text, { color: theme.colors.keyboardHint, dimColor: true, children: "h/l: days j/k: weeks" }), _jsx(Text, { color: theme.colors.keyboardHint, dimColor: true, children: "n/p: month T: today" })] })] }) }));
|
|
91
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Box } from "ink";
|
|
3
|
+
import { Text } from "../common/ThemedText";
|
|
4
|
+
import { useTheme } from "../../contexts/ThemeContext";
|
|
5
|
+
export const DayCell = ({ day }) => {
|
|
6
|
+
const { theme } = useTheme();
|
|
7
|
+
let textColor = theme.colors.calendarDayOtherMonth;
|
|
8
|
+
if (day.isCurrentMonth) {
|
|
9
|
+
textColor = day.hasTasks
|
|
10
|
+
? theme.colors.calendarDayWithTasks
|
|
11
|
+
: theme.colors.foreground;
|
|
12
|
+
}
|
|
13
|
+
// Determine indicators - selected takes priority, then today
|
|
14
|
+
const isSelected = day.isSelected;
|
|
15
|
+
const isToday = day.isToday && !day.isSelected;
|
|
16
|
+
if (isSelected) {
|
|
17
|
+
textColor = theme.colors.calendarSelected;
|
|
18
|
+
}
|
|
19
|
+
else if (day.isToday) {
|
|
20
|
+
textColor = theme.colors.calendarToday;
|
|
21
|
+
}
|
|
22
|
+
const dayNum = day.date.day.toString().padStart(2, " ");
|
|
23
|
+
const leftBracket = isSelected ? "[" : isToday ? "(" : " ";
|
|
24
|
+
const rightBracket = isSelected ? "]" : isToday ? ")" : " ";
|
|
25
|
+
return (_jsx(Box, { width: 4, children: _jsxs(Text, { color: textColor, children: [leftBracket, dayNum, rightBracket] }) }));
|
|
26
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box } from "ink";
|
|
3
|
+
import { Text } from "../common/ThemedText";
|
|
4
|
+
import { useTheme } from "../../contexts/ThemeContext";
|
|
5
|
+
import { DayCell } from "./DayCell";
|
|
6
|
+
export const MonthView = ({ calendarView }) => {
|
|
7
|
+
const { theme } = useTheme();
|
|
8
|
+
const dayHeaders = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"];
|
|
9
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: dayHeaders.map((day) => (_jsx(Box, { width: 4, children: _jsx(Text, { color: theme.colors.calendarHeader, bold: true, children: day }) }, day))) }), calendarView.weeks.map((week, weekIdx) => (_jsx(Box, { marginBottom: 0, children: week.map((day) => (_jsx(DayCell, { day: day }, day.dateString))) }, weekIdx)))] }));
|
|
10
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
interface BorderedBoxProps {
|
|
3
|
+
children: React.ReactNode;
|
|
4
|
+
borderColor: string;
|
|
5
|
+
backgroundColor?: string;
|
|
6
|
+
width?: number;
|
|
7
|
+
paddingX?: number;
|
|
8
|
+
paddingY?: number;
|
|
9
|
+
title?: string;
|
|
10
|
+
titleColor?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* A box component with double borders that properly applies backgroundColor
|
|
14
|
+
* to border characters. This fixes the issue where Ink's built-in borderStyle
|
|
15
|
+
* doesn't apply backgroundColor to border characters.
|
|
16
|
+
*/
|
|
17
|
+
export declare const BorderedBox: React.FC<BorderedBoxProps>;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
/**
|
|
4
|
+
* A box component with double borders that properly applies backgroundColor
|
|
5
|
+
* to border characters. This fixes the issue where Ink's built-in borderStyle
|
|
6
|
+
* doesn't apply backgroundColor to border characters.
|
|
7
|
+
*/
|
|
8
|
+
export const BorderedBox = ({ children, borderColor, backgroundColor, width, paddingX = 2, paddingY = 1, title, titleColor, }) => {
|
|
9
|
+
// Double border characters
|
|
10
|
+
const topLeft = "╔";
|
|
11
|
+
const topRight = "╗";
|
|
12
|
+
const bottomLeft = "╚";
|
|
13
|
+
const bottomRight = "╝";
|
|
14
|
+
const horizontal = "═";
|
|
15
|
+
const vertical = "║";
|
|
16
|
+
// Calculate inner width (content area)
|
|
17
|
+
// If width is specified, calculate inner width; otherwise use auto
|
|
18
|
+
const innerWidth = width ? width - 2 : undefined; // -2 for left and right borders
|
|
19
|
+
// Create horizontal border line
|
|
20
|
+
const createHorizontalBorder = (left, right, fill) => {
|
|
21
|
+
if (innerWidth) {
|
|
22
|
+
const fillCount = innerWidth;
|
|
23
|
+
return (_jsxs(Text, { color: borderColor, backgroundColor: backgroundColor, children: [left, fill.repeat(fillCount), right] }));
|
|
24
|
+
}
|
|
25
|
+
// For auto-width, we need to measure or use a reasonable default
|
|
26
|
+
return (_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: borderColor, backgroundColor: backgroundColor, children: left }), _jsx(Box, { flexGrow: 1, children: _jsx(Text, { color: borderColor, backgroundColor: backgroundColor, children: fill.repeat(40) }) }), _jsx(Text, { color: borderColor, backgroundColor: backgroundColor, children: right })] }));
|
|
27
|
+
};
|
|
28
|
+
return (_jsxs(Box, { flexDirection: "column", backgroundColor: backgroundColor, children: [createHorizontalBorder(topLeft, topRight, horizontal), _jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: borderColor, backgroundColor: backgroundColor, children: vertical }), _jsxs(Box, { flexDirection: "column", width: innerWidth, paddingX: paddingX, paddingY: paddingY, backgroundColor: backgroundColor, children: [title && (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: titleColor || borderColor, children: title }) })), children] }), _jsx(Text, { color: borderColor, backgroundColor: backgroundColor, children: vertical })] }), createHorizontalBorder(bottomLeft, bottomRight, horizontal)] }));
|
|
29
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text, useInput } from "ink";
|
|
3
|
+
import { useTheme } from "../../contexts/ThemeContext";
|
|
4
|
+
import { useApp } from "../../contexts/AppContext";
|
|
5
|
+
import { Modal } from "./Modal";
|
|
6
|
+
import { getDateString } from "../../utils/date";
|
|
7
|
+
import { format } from "date-fns";
|
|
8
|
+
export const ClearTimelineDialog = () => {
|
|
9
|
+
const { theme } = useTheme();
|
|
10
|
+
const { selectedDate, timeline, setShowClearTimelineDialog, clearTimelineForDate, } = useApp();
|
|
11
|
+
const date = new Date(selectedDate.year, selectedDate.month, selectedDate.day);
|
|
12
|
+
const dateStr = getDateString(date);
|
|
13
|
+
const formattedDate = format(date, "MMMM do, yyyy");
|
|
14
|
+
const eventCount = (timeline[dateStr] || []).length;
|
|
15
|
+
const handleConfirm = () => {
|
|
16
|
+
clearTimelineForDate(dateStr);
|
|
17
|
+
setShowClearTimelineDialog(false);
|
|
18
|
+
};
|
|
19
|
+
const handleCancel = () => {
|
|
20
|
+
setShowClearTimelineDialog(false);
|
|
21
|
+
};
|
|
22
|
+
useInput((input, key) => {
|
|
23
|
+
if (input.toLowerCase() === "y" || key.return) {
|
|
24
|
+
handleConfirm();
|
|
25
|
+
}
|
|
26
|
+
else if (input.toLowerCase() === "n" || key.escape) {
|
|
27
|
+
handleCancel();
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
return (_jsx(Modal, { children: _jsxs(Box, { flexDirection: "column", borderStyle: "double", borderColor: theme.colors.taskStateDelayed, paddingX: 4, paddingY: 2,
|
|
31
|
+
// @ts-ignore - backgroundColor is a valid Ink prop
|
|
32
|
+
backgroundColor: theme.colors.modalBackground || theme.colors.background, children: [_jsx(Box, { justifyContent: "center", marginBottom: 1, children: _jsx(Text, { bold: true, color: theme.colors.taskStateDelayed, children: "Clear Timeline" }) }), _jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { color: theme.colors.foreground, children: "You are about to clear the timeline for" }), _jsx(Box, { justifyContent: "center", marginY: 1, children: _jsx(Text, { bold: true, color: theme.colors.calendarHeader, children: formattedDate }) }), eventCount > 0 ? (_jsxs(Text, { color: theme.colors.keyboardHint, children: ["This will remove ", eventCount, " event", eventCount !== 1 ? "s" : "", " from the timeline."] })) : (_jsx(Text, { color: theme.colors.keyboardHint, dimColor: true, children: "The timeline is already empty." }))] }), _jsxs(Box, { marginTop: 2, justifyContent: "center", children: [_jsx(Text, { color: theme.colors.foreground, children: "Are you sure? " }), _jsx(Text, { color: theme.colors.taskStateCompleted, bold: true, children: "[Y]es" }), _jsx(Text, { color: theme.colors.foreground, children: " / " }), _jsx(Text, { color: theme.colors.taskStateDelayed, bold: true, children: "[N]o" })] }), _jsx(Box, { marginTop: 2, justifyContent: "center", children: _jsx(Text, { color: theme.colors.keyboardHint, dimColor: true, children: "Press Y to confirm, N or Esc to cancel" }) })] }) }));
|
|
33
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
interface FullscreenBackgroundProps {
|
|
3
|
+
children: React.ReactNode;
|
|
4
|
+
backgroundColor: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* A component that sets a fullscreen background color using Ink 6's
|
|
8
|
+
* native Box backgroundColor support. Supports hex colors, RGB, and named colors.
|
|
9
|
+
*/
|
|
10
|
+
export declare const FullscreenBackground: React.FC<FullscreenBackgroundProps>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Box, useStdout } from "ink";
|
|
3
|
+
/**
|
|
4
|
+
* A component that sets a fullscreen background color using Ink 6's
|
|
5
|
+
* native Box backgroundColor support. Supports hex colors, RGB, and named colors.
|
|
6
|
+
*/
|
|
7
|
+
export const FullscreenBackground = ({ children, backgroundColor, }) => {
|
|
8
|
+
const { stdout } = useStdout();
|
|
9
|
+
const width = stdout?.columns || 100;
|
|
10
|
+
const height = stdout?.rows || 30;
|
|
11
|
+
return (_jsx(Box, { flexDirection: "column", width: width, height: height, backgroundColor: backgroundColor, children: children }));
|
|
12
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import { useTheme } from "../../contexts/ThemeContext";
|
|
4
|
+
import { Modal } from "./Modal";
|
|
5
|
+
export const HelpDialog = () => {
|
|
6
|
+
const { theme } = useTheme();
|
|
7
|
+
const shortcuts = [
|
|
8
|
+
{ key: "Ctrl+C (twice)", action: "Quit application" },
|
|
9
|
+
{ key: "?", action: "Toggle help dialog" },
|
|
10
|
+
{ key: "Shift+;", action: "Show month overview" },
|
|
11
|
+
{ key: "t", action: "Select theme" },
|
|
12
|
+
{ key: "1", action: "Focus calendar pane" },
|
|
13
|
+
{ key: "2 / Tab", action: "Focus tasks pane" },
|
|
14
|
+
{ key: "3 / Shift+Tab", action: "Focus timeline pane" },
|
|
15
|
+
{ key: "", action: "" },
|
|
16
|
+
{ key: "Calendar Pane", action: "" },
|
|
17
|
+
{ key: "h/l ←/→", action: "Navigate days" },
|
|
18
|
+
{ key: "j/k ↓/↑", action: "Navigate weeks" },
|
|
19
|
+
{ key: "n/p", action: "Next/prev month" },
|
|
20
|
+
{ key: "T", action: "Go to today" },
|
|
21
|
+
{ key: "", action: "" },
|
|
22
|
+
{ key: "Tasks Pane", action: "" },
|
|
23
|
+
{ key: "j/k ↓/↑", action: "Navigate tasks" },
|
|
24
|
+
{ key: "a", action: "Add new task" },
|
|
25
|
+
{ key: "e", action: "Edit task" },
|
|
26
|
+
{ key: "d", action: "Delete task" },
|
|
27
|
+
{ key: "Space", action: "Toggle completion" },
|
|
28
|
+
{ key: "s", action: "Start task" },
|
|
29
|
+
{ key: "D", action: "Mark delegated" },
|
|
30
|
+
{ key: "x", action: "Mark delayed/cancelled" },
|
|
31
|
+
{ key: "Enter", action: "Expand/collapse subtasks" },
|
|
32
|
+
{ key: "", action: "" },
|
|
33
|
+
{ key: "Timeline Pane", action: "" },
|
|
34
|
+
{ key: "j/k", action: "Scroll timeline" },
|
|
35
|
+
{ key: "Shift+C", action: "Clear timeline" },
|
|
36
|
+
];
|
|
37
|
+
return (_jsx(Modal, { children: _jsxs(Box, { flexDirection: "column", borderStyle: "double", borderColor: theme.colors.helpDialogBorder, paddingX: 2, paddingY: 1,
|
|
38
|
+
// @ts-ignore - backgroundColor is a valid Ink prop
|
|
39
|
+
backgroundColor: theme.colors.modalBackground || theme.colors.background, children: [_jsx(Text, { bold: true, color: theme.colors.calendarHeader, children: "Keyboard Shortcuts" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: shortcuts.map((item, idx) => (_jsx(Box, { marginY: 0, children: item.key ? (_jsxs(_Fragment, { children: [_jsx(Box, { width: 20, children: _jsx(Text, { color: theme.colors.timelineEventStarted, children: item.key }) }), _jsx(Text, { color: theme.colors.foreground, children: item.action })] })) : (_jsx(Text, { color: theme.colors.separator, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" })) }, idx))) }), _jsx(Box, { marginY: 1, children: _jsx(Text, { color: theme.colors.keyboardHint, dimColor: true, children: "Press '?' to close" }) })] }) }));
|
|
40
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
interface ModalProps {
|
|
3
|
+
children: React.ReactNode;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* A fullscreen overlay modal component that centers its content.
|
|
7
|
+
* This creates a floating dialog effect similar to OpenCode's command palette.
|
|
8
|
+
*/
|
|
9
|
+
export declare const Modal: React.FC<ModalProps>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Box, useStdout } from "ink";
|
|
3
|
+
import { useTheme } from "../../contexts/ThemeContext";
|
|
4
|
+
import { FullscreenBackground } from "./FullscreenBackground";
|
|
5
|
+
/**
|
|
6
|
+
* A fullscreen overlay modal component that centers its content.
|
|
7
|
+
* This creates a floating dialog effect similar to OpenCode's command palette.
|
|
8
|
+
*/
|
|
9
|
+
export const Modal = ({ children }) => {
|
|
10
|
+
const { theme } = useTheme();
|
|
11
|
+
const { stdout } = useStdout();
|
|
12
|
+
const width = stdout?.columns || 100;
|
|
13
|
+
const height = stdout?.rows || 30;
|
|
14
|
+
return (_jsx(FullscreenBackground, { backgroundColor: theme.colors.modalOverlay || "black", children: _jsx(Box, { flexDirection: "column", width: width, height: height, justifyContent: "center", alignItems: "center", children: children }) }));
|
|
15
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import { useTheme } from "../../contexts/ThemeContext";
|
|
4
|
+
export const Separator = ({ vertical = true, height, width = 1, }) => {
|
|
5
|
+
const { theme } = useTheme();
|
|
6
|
+
if (vertical) {
|
|
7
|
+
return (_jsx(Box, { flexDirection: "column", width: width, height: height, children: _jsx(Text, { color: theme.colors.separator, children: "\u2502" }) }));
|
|
8
|
+
}
|
|
9
|
+
return (_jsx(Box, { width: width, children: _jsx(Text, { color: theme.colors.separator, children: "\u2500" }) }));
|
|
10
|
+
};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useMemo, useEffect } from "react";
|
|
3
|
+
import { Box, Text, useInput } from "ink";
|
|
4
|
+
import TextInput from "ink-text-input";
|
|
5
|
+
import { useTheme } from "../../contexts/ThemeContext";
|
|
6
|
+
import { useApp } from "../../contexts/AppContext";
|
|
7
|
+
import { Modal } from "./Modal";
|
|
8
|
+
import { getLightThemeNames, getDarkThemeNames } from "../../themes";
|
|
9
|
+
export const ThemeDialog = () => {
|
|
10
|
+
const { theme, setTheme, themeName } = useTheme();
|
|
11
|
+
const { setShowThemeDialog } = useApp();
|
|
12
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
13
|
+
const [focusMode, setFocusMode] = useState("search");
|
|
14
|
+
// Get organized theme lists
|
|
15
|
+
const lightThemes = useMemo(() => getLightThemeNames(), []);
|
|
16
|
+
const darkThemes = useMemo(() => getDarkThemeNames(), []);
|
|
17
|
+
// Combined list: light themes first, then dark themes
|
|
18
|
+
const allThemes = useMemo(() => {
|
|
19
|
+
// Filter themes based on search query
|
|
20
|
+
const query = searchQuery.toLowerCase();
|
|
21
|
+
const filteredLightThemes = lightThemes.filter((t) => t.toLowerCase().includes(query));
|
|
22
|
+
const filteredDarkThemes = darkThemes.filter((t) => t.toLowerCase().includes(query));
|
|
23
|
+
// Create a flat list with section markers
|
|
24
|
+
const items = [];
|
|
25
|
+
// Light themes section (only show if there are themes)
|
|
26
|
+
if (filteredLightThemes.length > 0) {
|
|
27
|
+
items.push({ type: "separator", value: "Light Themes" });
|
|
28
|
+
filteredLightThemes.forEach((t) => items.push({ type: "theme", value: t }));
|
|
29
|
+
}
|
|
30
|
+
// Dark themes section (only show if there are themes)
|
|
31
|
+
if (filteredDarkThemes.length > 0) {
|
|
32
|
+
items.push({ type: "separator", value: "Dark Themes" });
|
|
33
|
+
filteredDarkThemes.forEach((t) => items.push({ type: "theme", value: t }));
|
|
34
|
+
}
|
|
35
|
+
return items;
|
|
36
|
+
}, [lightThemes, darkThemes, searchQuery]);
|
|
37
|
+
// Get only theme items (for navigation)
|
|
38
|
+
const themeItems = useMemo(() => allThemes.filter((item) => item.type === "theme"), [allThemes]);
|
|
39
|
+
// Find initial selected index
|
|
40
|
+
const initialIndex = useMemo(() => {
|
|
41
|
+
const idx = themeItems.findIndex((item) => item.value === themeName);
|
|
42
|
+
return idx >= 0 ? idx : 0;
|
|
43
|
+
}, [themeItems, themeName]);
|
|
44
|
+
const [selectedIndex, setSelectedIndex] = useState(initialIndex);
|
|
45
|
+
// Reset selection when search query changes to highlight the first result
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
setSelectedIndex(0);
|
|
48
|
+
}, [searchQuery]);
|
|
49
|
+
useInput((input, key) => {
|
|
50
|
+
// Escape closes dialog from both modes
|
|
51
|
+
if (key.escape) {
|
|
52
|
+
setShowThemeDialog(false);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// Handle navigation based on focus mode
|
|
56
|
+
if (focusMode === "search") {
|
|
57
|
+
// Enter selects the first result and closes
|
|
58
|
+
if (key.return && themeItems.length > 0) {
|
|
59
|
+
setTheme(themeItems[0].value);
|
|
60
|
+
setShowThemeDialog(false);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
// In search mode, only down arrow moves to list (to avoid j/k interfering with typing)
|
|
64
|
+
if (key.downArrow && themeItems.length > 0) {
|
|
65
|
+
setFocusMode("list");
|
|
66
|
+
setSelectedIndex(0);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
// In list mode
|
|
72
|
+
if (key.upArrow || input === "k") {
|
|
73
|
+
if (selectedIndex > 0) {
|
|
74
|
+
setSelectedIndex((prev) => prev - 1);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
// At top of list, move back to search
|
|
78
|
+
setFocusMode("search");
|
|
79
|
+
}
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (key.downArrow || input === "j") {
|
|
83
|
+
setSelectedIndex((prev) => prev < themeItems.length - 1 ? prev + 1 : 0);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
// Enter selects theme only in list mode
|
|
87
|
+
if (key.return && themeItems.length > 0) {
|
|
88
|
+
setTheme(themeItems[selectedIndex].value);
|
|
89
|
+
setShowThemeDialog(false);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}, { isActive: true });
|
|
94
|
+
// Get the currently selected theme name
|
|
95
|
+
const selectedThemeName = themeItems[selectedIndex]?.value;
|
|
96
|
+
// Format display name
|
|
97
|
+
const formatThemeName = (name) => {
|
|
98
|
+
return name
|
|
99
|
+
.split("-")
|
|
100
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
101
|
+
.join(" ");
|
|
102
|
+
};
|
|
103
|
+
return (_jsx(Modal, { children: _jsxs(Box, { flexDirection: "column", borderStyle: "double", borderColor: theme.colors.helpDialogBorder, paddingX: 2, paddingY: 1, width: 44,
|
|
104
|
+
// @ts-ignore - backgroundColor is a valid Ink prop
|
|
105
|
+
backgroundColor: theme.colors.modalBackground || theme.colors.background, children: [_jsx(Text, { bold: true, color: theme.colors.calendarHeader, underline: true, children: "Select Theme" }), _jsxs(Box, { marginTop: 1, flexDirection: "row", alignItems: "center", children: [_jsxs(Text, { color: theme.colors.foreground, dimColor: true, children: ["Search:", " "] }), _jsx(Text, { color: theme.colors.foreground, children: _jsx(TextInput, { value: searchQuery, onChange: setSearchQuery, focus: focusMode === "search", placeholder: "Type to filter..." }) })] }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: allThemes.map((item, idx) => {
|
|
106
|
+
if (item.type === "separator") {
|
|
107
|
+
return (_jsx(Box, { marginTop: idx > 0 ? 1 : 0, marginBottom: 0, children: _jsx(Text, { bold: true, color: theme.colors.calendarHeader, dimColor: true, children: item.value }) }, `sep-${idx}`));
|
|
108
|
+
}
|
|
109
|
+
const isSelected = item.value === selectedThemeName;
|
|
110
|
+
const isCurrent = item.value === themeName;
|
|
111
|
+
return (_jsx(Box, { paddingLeft: 1, children: _jsxs(Text, { color: isSelected
|
|
112
|
+
? theme.colors.focusIndicator
|
|
113
|
+
: theme.colors.foreground, bold: isSelected, children: [isSelected ? "➜ " : " ", formatThemeName(item.value), isCurrent ? " (current)" : ""] }) }, item.value));
|
|
114
|
+
}) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.colors.keyboardHint, dimColor: true, children: focusMode === "search"
|
|
115
|
+
? "Type to search • ↓ to navigate list • Esc to close"
|
|
116
|
+
: "↑/↓ or k/j to navigate • Enter to select • Esc to close" }) })] }) }));
|
|
117
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { useTheme } from "../../contexts/ThemeContext";
|
|
3
|
+
interface ThemedScreenProps {
|
|
4
|
+
children: React.ReactNode;
|
|
5
|
+
backgroundColor?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* A wrapper component that applies the theme's background color to the entire screen.
|
|
9
|
+
*
|
|
10
|
+
* This works by:
|
|
11
|
+
* 1. Getting the terminal dimensions
|
|
12
|
+
* 2. Rendering the children
|
|
13
|
+
* 3. For any empty space, we don't try to fill it (terminals control their own bg)
|
|
14
|
+
*
|
|
15
|
+
* The key insight: we can't change the terminal's bg, but we CAN ensure all our
|
|
16
|
+
* Text elements have a consistent background by using a context or global style.
|
|
17
|
+
*/
|
|
18
|
+
export declare const ThemedScreen: React.FC<ThemedScreenProps>;
|
|
19
|
+
/**
|
|
20
|
+
* Export the theme context hook for components that need to apply backgrounds
|
|
21
|
+
*/
|
|
22
|
+
export { useTheme };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text, useStdout } from "ink";
|
|
3
|
+
import { useTheme } from "../../contexts/ThemeContext";
|
|
4
|
+
/**
|
|
5
|
+
* A wrapper component that applies the theme's background color to the entire screen.
|
|
6
|
+
*
|
|
7
|
+
* This works by:
|
|
8
|
+
* 1. Getting the terminal dimensions
|
|
9
|
+
* 2. Rendering the children
|
|
10
|
+
* 3. For any empty space, we don't try to fill it (terminals control their own bg)
|
|
11
|
+
*
|
|
12
|
+
* The key insight: we can't change the terminal's bg, but we CAN ensure all our
|
|
13
|
+
* Text elements have a consistent background by using a context or global style.
|
|
14
|
+
*/
|
|
15
|
+
export const ThemedScreen = ({ children, backgroundColor, }) => {
|
|
16
|
+
const { stdout } = useStdout();
|
|
17
|
+
const { theme } = useTheme();
|
|
18
|
+
const width = stdout?.columns || 100;
|
|
19
|
+
const height = stdout?.rows || 30;
|
|
20
|
+
// For 'terminal' theme, don't try to set any background
|
|
21
|
+
// For other themes, we simply render - the Text components should handle their own bg
|
|
22
|
+
const bgColor = backgroundColor ||
|
|
23
|
+
(theme.name === "terminal" ? undefined : theme.colors.background);
|
|
24
|
+
if (!bgColor) {
|
|
25
|
+
// Terminal theme - no background styling, use terminal's native colors
|
|
26
|
+
return (_jsx(Box, { flexDirection: "column", width: width, height: height, children: children }));
|
|
27
|
+
}
|
|
28
|
+
// For light/dark themes, we need to fill the background
|
|
29
|
+
// The trick: Use ANSI escape codes to fill the entire terminal background
|
|
30
|
+
// This ensures even the border areas get the background color
|
|
31
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { position: "absolute", width: width, height: height, children: Array.from({ length: height }).map((_, i) => (_jsx(Box, { width: width, children: _jsx(Text, { backgroundColor: bgColor, children: " ".repeat(width) }) }, `bg-row-${i}`))) }), _jsx(Box, { position: "relative", width: width, height: height, children: children })] }));
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Export the theme context hook for components that need to apply backgrounds
|
|
35
|
+
*/
|
|
36
|
+
export { useTheme };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { TextProps } from "ink";
|
|
3
|
+
/**
|
|
4
|
+
* A drop-in replacement for Ink's Text component that automatically applies
|
|
5
|
+
* the theme's background color. This ensures text is visible on themed backgrounds.
|
|
6
|
+
*
|
|
7
|
+
* Usage: Replace `import { Text } from 'ink'` with
|
|
8
|
+
* `import { Text } from '../common/ThemedText'`
|
|
9
|
+
*/
|
|
10
|
+
export declare const ThemedText: React.FC<TextProps>;
|
|
11
|
+
export { ThemedText as Text };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Text } from "ink";
|
|
3
|
+
import { useTheme } from "../../contexts/ThemeContext";
|
|
4
|
+
/**
|
|
5
|
+
* A drop-in replacement for Ink's Text component that automatically applies
|
|
6
|
+
* the theme's background color. This ensures text is visible on themed backgrounds.
|
|
7
|
+
*
|
|
8
|
+
* Usage: Replace `import { Text } from 'ink'` with
|
|
9
|
+
* `import { Text } from '../common/ThemedText'`
|
|
10
|
+
*/
|
|
11
|
+
export const ThemedText = ({ children, backgroundColor, ...props }) => {
|
|
12
|
+
const { theme } = useTheme();
|
|
13
|
+
// Only apply theme background for non-terminal themes
|
|
14
|
+
// If an explicit backgroundColor is passed, use that instead
|
|
15
|
+
const bgColor = backgroundColor ??
|
|
16
|
+
(theme.name !== "terminal" ? theme.colors.background : undefined);
|
|
17
|
+
return (_jsx(Text, { ...props, backgroundColor: bgColor, children: children }));
|
|
18
|
+
};
|
|
19
|
+
// Re-export as Text for easy drop-in replacement
|
|
20
|
+
export { ThemedText as Text };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
interface PaneProps {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
title?: string;
|
|
6
|
+
isFocused?: boolean;
|
|
7
|
+
width?: number | string;
|
|
8
|
+
height?: number | string;
|
|
9
|
+
center?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare const Pane: React.FC<PaneProps>;
|
|
12
|
+
export {};
|