floq 0.5.0 → 0.6.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.
@@ -0,0 +1,53 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { useTheme } from '../theme/index.js';
4
+ /**
5
+ * Dragon Quest style multi-window layout
6
+ *
7
+ * ╭────────────────────────────────────────────╮
8
+ * │ FLOQ - GTD Manager │
9
+ * ╰────────────────────────────────────────────╯
10
+ * ╭─ コマンド ─╮ ╭─ Inbox ──────────────────────╮
11
+ * │ ▶ Inbox │ │ ▶ タスク1 │
12
+ * │ 次 │ │ タスク2 │
13
+ * │ 待ち │ │ タスク3 │
14
+ * │ いつか │ ╰────────────────────────────────╯
15
+ * │ 完了 │
16
+ * ╰───────────╯
17
+ */
18
+ export function DQLayout({ title, subtitle, menuTitle, menuItems, contentTitle, children, statusTitle, statusItems, footer, }) {
19
+ const theme = useTheme();
20
+ const borderColor = theme.colors.border;
21
+ const activeColor = theme.colors.borderActive;
22
+ // Render a window with title on border
23
+ const renderWindow = (windowTitle, content, options = {}) => {
24
+ const { width, minHeight = 3, active = false, flexGrow } = options;
25
+ const color = active ? activeColor : borderColor;
26
+ const titleDisplay = ` ${windowTitle} `;
27
+ const minW = width || 20;
28
+ return (_jsxs(Box, { flexDirection: "column", width: width, minHeight: minHeight, flexGrow: flexGrow, children: [_jsxs(Box, { children: [_jsx(Text, { color: color, children: "\u256D\u2500\u2500" }), _jsx(Text, { color: theme.colors.text, bold: true, children: titleDisplay }), _jsxs(Text, { color: color, children: ['─'.repeat(Math.max(minW - titleDisplay.length - 6, 2)), "\u256E"] })] }), _jsx(Box, { flexDirection: "column", flexGrow: 1, children: _jsxs(Box, { flexDirection: "row", flexGrow: 1, children: [_jsx(Text, { color: color, children: "\u2502 " }), _jsx(Box, { flexDirection: "column", flexGrow: 1, children: content }), _jsx(Text, { color: color, children: " \u2502" })] }) }), _jsx(Box, { children: _jsxs(Text, { color: color, children: ["\u2570", '─'.repeat(Math.max(minW - 2, 2)), "\u256F"] }) })] }));
29
+ };
30
+ // Render header window
31
+ const renderHeader = () => {
32
+ const headerWidth = 50;
33
+ const padding = Math.floor((headerWidth - title.length - 4) / 2);
34
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Box, { children: _jsxs(Text, { color: borderColor, children: ["\u256D", '─'.repeat(headerWidth - 2), "\u256E"] }) }), _jsxs(Box, { children: [_jsx(Text, { color: borderColor, children: "\u2502" }), _jsxs(Text, { color: theme.colors.primary, bold: true, children: [' '.repeat(padding), title, ' '.repeat(padding)] }), _jsx(Text, { color: borderColor, children: "\u2502" })] }), subtitle && (_jsxs(Box, { children: [_jsx(Text, { color: borderColor, children: "\u2502" }), _jsxs(Text, { color: theme.colors.textMuted, children: [' '.repeat(Math.floor((headerWidth - subtitle.length - 4) / 2)), subtitle] }), _jsx(Text, { color: borderColor, children: "\u2502" })] })), _jsx(Box, { children: _jsxs(Text, { color: borderColor, children: ["\u2570", '─'.repeat(headerWidth - 2), "\u256F"] }) })] }));
35
+ };
36
+ // Render menu items
37
+ const renderMenuContent = () => (_jsx(_Fragment, { children: menuItems.map((item, index) => (_jsx(Box, { children: _jsxs(Text, { color: item.isActive ? theme.colors.textSelected : theme.colors.text, bold: item.isActive, children: [item.isActive ? theme.style.selectedPrefix : theme.style.unselectedPrefix, item.label, item.count !== undefined && ` (${item.count})`] }) }, index))) }));
38
+ // Render status window
39
+ const renderStatus = () => {
40
+ if (!statusTitle || !statusItems)
41
+ return null;
42
+ return (_jsx(Box, { marginTop: 1, children: renderWindow(statusTitle, _jsx(_Fragment, { children: statusItems.map((item, index) => (_jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.textMuted, children: [item.label, ": "] }), _jsx(Text, { color: theme.colors.accent, children: item.value })] }, index))) }), { width: 20, minHeight: statusItems.length + 2 }) }));
43
+ };
44
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [renderHeader(), _jsxs(Box, { flexDirection: "row", children: [_jsxs(Box, { flexDirection: "column", marginRight: 1, children: [renderWindow(menuTitle, renderMenuContent(), {
45
+ width: 18,
46
+ minHeight: menuItems.length + 2,
47
+ active: true,
48
+ }), renderStatus()] }), _jsx(Box, { flexDirection: "column", flexGrow: 1, children: renderWindow(contentTitle, children, {
49
+ minHeight: 12,
50
+ active: true,
51
+ flexGrow: 1,
52
+ }) })] }), footer && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.colors.textMuted, children: footer }) }))] }));
53
+ }
@@ -0,0 +1,53 @@
1
+ import React from 'react';
2
+ import type { Task } from '../../db/schema.js';
3
+ import type { ProjectProgress } from './TaskItem.js';
4
+ interface DQTaskListProps {
5
+ tasks: Task[];
6
+ selectedIndex: number;
7
+ emptyMessage: string;
8
+ showProject?: boolean;
9
+ getParentProject?: (parentId: string | null) => Task | undefined;
10
+ projectProgress?: Record<string, ProjectProgress>;
11
+ isProjectTab?: boolean;
12
+ }
13
+ /**
14
+ * Dragon Quest style task list inside a window
15
+ */
16
+ export declare function DQTaskList({ tasks, selectedIndex, emptyMessage, showProject, getParentProject, projectProgress, isProjectTab, }: DQTaskListProps): React.ReactElement;
17
+ interface DQWindowFrameProps {
18
+ title: string;
19
+ children: React.ReactNode;
20
+ width?: number | string;
21
+ minHeight?: number;
22
+ active?: boolean;
23
+ flexGrow?: number;
24
+ }
25
+ /**
26
+ * Dragon Quest style window frame with title on border
27
+ */
28
+ export declare function DQWindowFrame({ title, children, width, minHeight, active, flexGrow, }: DQWindowFrameProps): React.ReactElement;
29
+ interface DQMenuProps {
30
+ title: string;
31
+ items: Array<{
32
+ key: string;
33
+ label: string;
34
+ count: number;
35
+ isActive: boolean;
36
+ }>;
37
+ width?: number;
38
+ }
39
+ /**
40
+ * Dragon Quest style menu (left side panel)
41
+ */
42
+ export declare function DQMenu({ title, items, width }: DQMenuProps): React.ReactElement;
43
+ interface DQHeaderProps {
44
+ title: string;
45
+ version: string;
46
+ dbMode: string;
47
+ contextFilter?: string | null;
48
+ }
49
+ /**
50
+ * Dragon Quest style header window
51
+ */
52
+ export declare function DQHeader({ title, version, dbMode, contextFilter }: DQHeaderProps): React.ReactElement;
53
+ export {};
@@ -0,0 +1,48 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { useTheme } from '../theme/index.js';
4
+ /**
5
+ * Dragon Quest style task list inside a window
6
+ */
7
+ export function DQTaskList({ tasks, selectedIndex, emptyMessage, showProject = false, getParentProject, projectProgress, isProjectTab = false, }) {
8
+ const theme = useTheme();
9
+ if (tasks.length === 0) {
10
+ return (_jsx(Text, { color: theme.colors.textMuted, italic: true, children: emptyMessage }));
11
+ }
12
+ return (_jsx(_Fragment, { children: tasks.map((task, index) => {
13
+ const isSelected = index === selectedIndex;
14
+ const parentProject = showProject && getParentProject ? getParentProject(task.parentId) : undefined;
15
+ const progress = isProjectTab && projectProgress ? projectProgress[task.id] : undefined;
16
+ return (_jsx(Box, { flexDirection: "row", children: _jsxs(Text, { color: isSelected ? theme.colors.textSelected : theme.colors.text, bold: isSelected, children: [isSelected ? theme.style.selectedPrefix : theme.style.unselectedPrefix, task.title, task.waitingFor && (_jsxs(Text, { color: theme.colors.statusWaiting, children: [" [", task.waitingFor, "]"] })), task.context && (_jsxs(Text, { color: theme.colors.accent, children: [" @", task.context] })), parentProject && (_jsxs(Text, { color: theme.colors.textMuted, children: [" \u2190 ", parentProject.title] })), progress && (_jsxs(Text, { color: theme.colors.textMuted, children: [" [", progress.completed, "/", progress.total, "]"] }))] }) }, task.id));
17
+ }) }));
18
+ }
19
+ /**
20
+ * Dragon Quest style window frame with title on border
21
+ */
22
+ export function DQWindowFrame({ title, children, width, minHeight = 10, active = false, flexGrow, }) {
23
+ const theme = useTheme();
24
+ const borderColor = active ? theme.colors.borderActive : theme.colors.border;
25
+ const titleText = ` ${title} `;
26
+ return (_jsxs(Box, { flexDirection: "column", width: width, minHeight: minHeight, flexGrow: flexGrow, children: [_jsxs(Text, { color: borderColor, children: ["\u256D\u2500\u2500", _jsx(Text, { color: theme.colors.text, bold: true, children: titleText }), _jsx(Text, { color: borderColor, children: '─'.repeat(30) })] }), _jsxs(Box, { flexDirection: "row", flexGrow: 1, children: [_jsx(Text, { color: borderColor, children: "\u2502 " }), _jsx(Box, { flexDirection: "column", flexGrow: 1, children: children }), _jsx(Text, { color: borderColor, children: " \u2502" })] }), _jsxs(Text, { color: borderColor, children: ["\u2570", '─'.repeat(titleText.length + 32), "\u256F"] })] }));
27
+ }
28
+ /**
29
+ * Dragon Quest style menu (left side panel)
30
+ */
31
+ export function DQMenu({ title, items, width = 16 }) {
32
+ const theme = useTheme();
33
+ const borderColor = theme.colors.border;
34
+ const titleText = ` ${title} `;
35
+ const innerWidth = width - 4;
36
+ return (_jsxs(Box, { flexDirection: "column", width: width, children: [_jsxs(Text, { color: borderColor, children: ["\u256D\u2500", _jsx(Text, { color: theme.colors.text, bold: true, children: titleText }), _jsxs(Text, { color: borderColor, children: ['─'.repeat(Math.max(innerWidth - titleText.length, 0)), "\u256E"] })] }), items.map((item) => (_jsxs(Box, { children: [_jsx(Text, { color: borderColor, children: "\u2502" }), _jsxs(Text, { color: item.isActive ? theme.colors.textSelected : theme.colors.text, bold: item.isActive, children: [item.isActive ? theme.style.selectedPrefix : theme.style.unselectedPrefix, item.label] }), _jsxs(Text, { color: theme.colors.textMuted, children: ["(", item.count, ")"] }), _jsx(Text, { color: borderColor, children: "\u2502" })] }, item.key))), _jsxs(Text, { color: borderColor, children: ["\u2570", '─'.repeat(innerWidth + 2), "\u256F"] })] }));
37
+ }
38
+ /**
39
+ * Dragon Quest style header window
40
+ */
41
+ export function DQHeader({ title, version, dbMode, contextFilter }) {
42
+ const theme = useTheme();
43
+ const borderColor = theme.colors.border;
44
+ const headerText = `${title} ver.${version}`;
45
+ const width = 50;
46
+ const padding = Math.floor((width - headerText.length - 2) / 2);
47
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { color: borderColor, children: ["\u256D", '─'.repeat(width - 2), "\u256E"] }), _jsxs(Box, { children: [_jsx(Text, { color: borderColor, children: "\u2502" }), _jsxs(Text, { color: theme.colors.primary, bold: true, children: [' '.repeat(padding), headerText, ' '.repeat(padding)] }), _jsx(Text, { color: borderColor, children: "\u2502" })] }), _jsxs(Box, { children: [_jsx(Text, { color: borderColor, children: "\u2502" }), _jsxs(Text, { children: [' '.repeat(Math.floor((width - 20) / 2)), _jsxs(Text, { color: theme.colors.textMuted, children: ["[", dbMode, "]"] }), contextFilter !== null && contextFilter !== undefined && (_jsxs(Text, { color: theme.colors.accent, children: [" @", contextFilter === '' ? 'none' : contextFilter] }))] }), _jsx(Text, { color: borderColor, children: "\u2502" })] }), _jsxs(Text, { color: borderColor, children: ["\u2570", '─'.repeat(width - 2), "\u256F"] })] }));
48
+ }
@@ -0,0 +1,19 @@
1
+ import React from 'react';
2
+ interface DQWindowProps {
3
+ title?: string;
4
+ children: React.ReactNode;
5
+ width?: number | string;
6
+ height?: number | string;
7
+ minHeight?: number;
8
+ minWidth?: number;
9
+ active?: boolean;
10
+ }
11
+ /**
12
+ * Dragon Quest style window with title on the border
13
+ *
14
+ * ╭─ Title ──────────╮
15
+ * │ Content here │
16
+ * ╰──────────────────╯
17
+ */
18
+ export declare function DQWindow({ title, children, width, height, minHeight, minWidth, active, }: DQWindowProps): React.ReactElement;
19
+ export {};
@@ -0,0 +1,33 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { useTheme } from '../theme/index.js';
4
+ /**
5
+ * Dragon Quest style window with title on the border
6
+ *
7
+ * ╭─ Title ──────────╮
8
+ * │ Content here │
9
+ * ╰──────────────────╯
10
+ */
11
+ export function DQWindow({ title, children, width, height, minHeight, minWidth, active = false, }) {
12
+ const theme = useTheme();
13
+ const borderColor = active ? theme.colors.borderActive : theme.colors.border;
14
+ // Build the top border with title embedded
15
+ const renderTopBorder = (contentWidth) => {
16
+ if (!title) {
17
+ // No title - just return standard corner
18
+ return null;
19
+ }
20
+ const titleText = ` ${title} `;
21
+ const titleLen = titleText.length;
22
+ // Calculate dashes needed (minimum 2 on each side of title)
23
+ const availableWidth = Math.max(contentWidth - titleLen - 4, 0);
24
+ const leftDashes = 2;
25
+ const rightDashes = Math.max(availableWidth - leftDashes, 2);
26
+ return (_jsxs(Box, { children: [_jsxs(Text, { color: borderColor, children: ["\u256D", '─'.repeat(leftDashes)] }), _jsx(Text, { color: theme.colors.text, bold: true, children: titleText }), _jsxs(Text, { color: borderColor, children: ['─'.repeat(rightDashes), "\u256E"] })] }));
27
+ };
28
+ const renderBottomBorder = (contentWidth) => {
29
+ return (_jsx(Box, { children: _jsxs(Text, { color: borderColor, children: ["\u2570", '─'.repeat(contentWidth), "\u256F"] }) }));
30
+ };
31
+ // For DQ style, we render custom borders
32
+ return (_jsxs(Box, { flexDirection: "column", width: width, height: height, minHeight: minHeight, minWidth: minWidth, children: [title && renderTopBorder(typeof minWidth === 'number' ? minWidth - 2 : 20), _jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: borderColor, children: "\u2502" }), _jsx(Box, { flexDirection: "column", flexGrow: 1, paddingX: 1, children: children }), _jsx(Text, { color: borderColor, children: "\u2502" })] }), renderBottomBorder(typeof minWidth === 'number' ? minWidth - 2 : 20)] }));
33
+ }
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ type SettingsMode = 'none' | 'theme-select' | 'mode-select' | 'lang-select';
3
+ interface GtdDQProps {
4
+ onOpenSettings?: (mode: SettingsMode) => void;
5
+ }
6
+ export declare function GtdDQ({ onOpenSettings }: GtdDQProps): React.ReactElement;
7
+ export {};