File size: 3,013 Bytes
9332ccf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import type React from 'react';
import { useMemo } from 'react';
import { Box, Text } from 'ink';
import { theme } from '../semantic-colors.js';
import { ChecklistItem, type ChecklistItemData } from './ChecklistItem.js';

export interface ChecklistProps {
  title: string;
  items: ChecklistItemData[];
  isExpanded: boolean;
  toggleHint?: string;
}

const ChecklistTitleDisplay: React.FC<{
  title: string;
  items: ChecklistItemData[];
  toggleHint?: string;
}> = ({ title, items, toggleHint }) => {
  const score = useMemo(() => {
    let total = 0;
    let completed = 0;
    for (const item of items) {
      if (item.status !== 'cancelled') {
        total += 1;
        if (item.status === 'completed') {
          completed += 1;
        }
      }
    }
    return `${completed}/${total} completed`;
  }, [items]);

  return (
    <Box flexDirection="row" columnGap={2} height={1}>
      <Text color={theme.text.primary} bold aria-label={`${title} list`}>
        {title}
      </Text>
      <Text color={theme.text.secondary}>
        {score}
        {toggleHint ? ` (${toggleHint})` : ''}
      </Text>
    </Box>
  );
};

const ChecklistListDisplay: React.FC<{ items: ChecklistItemData[] }> = ({
  items,
}) => (
  <Box flexDirection="column" aria-role="list">
    {items.map((item, index) => (
      <ChecklistItem
        item={item}
        key={`${index}-${item.label}`}
        role="listitem"
      />
    ))}
  </Box>
);

export const Checklist: React.FC<ChecklistProps> = ({
  title,
  items,
  isExpanded,
  toggleHint,
}) => {
  const inProgress: ChecklistItemData | null = useMemo(
    () => items.find((item) => item.status === 'in_progress') || null,
    [items],
  );

  const hasActiveItems = useMemo(
    () =>
      items.some(
        (item) => item.status === 'pending' || item.status === 'in_progress',
      ),
    [items],
  );

  if (items.length === 0 || (!isExpanded && !hasActiveItems)) {
    return null;
  }

  return (
    <Box
      borderStyle="single"
      borderBottom={false}
      borderRight={false}
      borderLeft={false}
      borderColor={theme.border.default}
      paddingLeft={1}
      paddingRight={1}
    >
      {isExpanded ? (
        <Box flexDirection="column" rowGap={1}>
          <ChecklistTitleDisplay
            title={title}
            items={items}
            toggleHint={toggleHint}
          />
          <ChecklistListDisplay items={items} />
        </Box>
      ) : (
        <Box flexDirection="row" columnGap={1} height={1}>
          <Box flexShrink={0} flexGrow={0}>
            <ChecklistTitleDisplay
              title={title}
              items={items}
              toggleHint={toggleHint}
            />
          </Box>
          {inProgress && (
            <Box flexShrink={1} flexGrow={1}>
              <ChecklistItem item={inProgress} wrap="truncate" />
            </Box>
          )}
        </Box>
      )}
    </Box>
  );
};