termcast 1.3.48 → 1.3.50
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/build.d.ts.map +1 -1
- package/dist/build.js +12 -0
- package/dist/build.js.map +1 -1
- package/dist/cli.js +5 -40
- package/dist/cli.js.map +1 -1
- package/dist/colors.d.ts +7 -7
- package/dist/colors.js +7 -7
- package/dist/compile.d.ts +6 -1
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +45 -26
- package/dist/compile.js.map +1 -1
- package/dist/components/actions.js +1 -1
- package/dist/components/actions.js.map +1 -1
- package/dist/components/bar-chart.d.ts +38 -0
- package/dist/components/bar-chart.d.ts.map +1 -0
- package/dist/components/bar-chart.js +158 -0
- package/dist/components/bar-chart.js.map +1 -0
- package/dist/components/bar-graph.d.ts +41 -0
- package/dist/components/bar-graph.d.ts.map +1 -0
- package/dist/components/bar-graph.js +95 -0
- package/dist/components/bar-graph.js.map +1 -0
- package/dist/components/detail.d.ts.map +1 -1
- package/dist/components/detail.js +5 -7
- package/dist/components/detail.js.map +1 -1
- package/dist/components/footer.d.ts.map +1 -1
- package/dist/components/footer.js +8 -9
- package/dist/components/footer.js.map +1 -1
- package/dist/components/form/date-picker.d.ts.map +1 -1
- package/dist/components/form/date-picker.js +7 -1
- package/dist/components/form/date-picker.js.map +1 -1
- package/dist/components/form/dropdown.d.ts.map +1 -1
- package/dist/components/form/dropdown.js +10 -2
- package/dist/components/form/dropdown.js.map +1 -1
- package/dist/components/form/index.d.ts.map +1 -1
- package/dist/components/form/index.js +4 -5
- package/dist/components/form/index.js.map +1 -1
- package/dist/components/form/use-form-navigation.d.ts.map +1 -1
- package/dist/components/form/use-form-navigation.js +6 -0
- package/dist/components/form/use-form-navigation.js.map +1 -1
- package/dist/components/graph.d.ts +111 -0
- package/dist/components/graph.d.ts.map +1 -0
- package/dist/components/graph.js +392 -0
- package/dist/components/graph.js.map +1 -0
- package/dist/components/icon.js +5 -5
- package/dist/components/icon.js.map +1 -1
- package/dist/components/list.d.ts +53 -5
- package/dist/components/list.d.ts.map +1 -1
- package/dist/components/list.js +125 -71
- package/dist/components/list.js.map +1 -1
- package/dist/components/loading-bar.js +3 -3
- package/dist/components/loading-bar.js.map +1 -1
- package/dist/components/loading-text.d.ts +1 -1
- package/dist/components/loading-text.d.ts.map +1 -1
- package/dist/components/loading-text.js +3 -1
- package/dist/components/loading-text.js.map +1 -1
- package/dist/components/metadata.js +2 -2
- package/dist/components/metadata.js.map +1 -1
- package/dist/components/row.d.ts +10 -0
- package/dist/components/row.d.ts.map +1 -0
- package/dist/components/row.js +12 -0
- package/dist/components/row.js.map +1 -0
- package/dist/components/table.d.ts +57 -0
- package/dist/components/table.d.ts.map +1 -0
- package/dist/components/table.js +365 -0
- package/dist/components/table.js.map +1 -0
- package/dist/descendants.js +13 -13
- package/dist/descendants.js.map +1 -1
- package/dist/examples/bar-graph-weekly.d.ts +2 -0
- package/dist/examples/bar-graph-weekly.d.ts.map +1 -0
- package/dist/examples/bar-graph-weekly.js +95 -0
- package/dist/examples/bar-graph-weekly.js.map +1 -0
- package/dist/examples/components-weird-places.d.ts +2 -0
- package/dist/examples/components-weird-places.d.ts.map +1 -0
- package/dist/examples/components-weird-places.js +46 -0
- package/dist/examples/components-weird-places.js.map +1 -0
- package/dist/examples/graph-bar-chart.d.ts +2 -0
- package/dist/examples/graph-bar-chart.d.ts.map +1 -0
- package/dist/examples/graph-bar-chart.js +270 -0
- package/dist/examples/graph-bar-chart.js.map +1 -0
- package/dist/examples/graph-multi-series.d.ts +2 -0
- package/dist/examples/graph-multi-series.d.ts.map +1 -0
- package/dist/examples/graph-multi-series.js +23 -0
- package/dist/examples/graph-multi-series.js.map +1 -0
- package/dist/examples/graph-polymarket.d.ts +2 -0
- package/dist/examples/graph-polymarket.d.ts.map +1 -0
- package/dist/examples/graph-polymarket.js +109 -0
- package/dist/examples/graph-polymarket.js.map +1 -0
- package/dist/examples/graph-row.d.ts +2 -0
- package/dist/examples/graph-row.d.ts.map +1 -0
- package/dist/examples/graph-row.js +226 -0
- package/dist/examples/graph-row.js.map +1 -0
- package/dist/examples/graph-styles.d.ts +2 -0
- package/dist/examples/graph-styles.d.ts.map +1 -0
- package/dist/examples/graph-styles.js +316 -0
- package/dist/examples/graph-styles.js.map +1 -0
- package/dist/examples/list-accessory-table.d.ts +2 -0
- package/dist/examples/list-accessory-table.d.ts.map +1 -0
- package/dist/examples/list-accessory-table.js +46 -0
- package/dist/examples/list-accessory-table.js.map +1 -0
- package/dist/examples/list-item-accessories.d.ts +2 -0
- package/dist/examples/list-item-accessories.d.ts.map +1 -0
- package/dist/examples/list-item-accessories.js +27 -0
- package/dist/examples/list-item-accessories.js.map +1 -0
- package/dist/examples/list-no-actions.d.ts +2 -0
- package/dist/examples/list-no-actions.d.ts.map +1 -0
- package/dist/examples/list-no-actions.js +7 -0
- package/dist/examples/list-no-actions.js.map +1 -0
- package/dist/examples/simple-detail-table.d.ts +2 -0
- package/dist/examples/simple-detail-table.d.ts.map +1 -0
- package/dist/examples/simple-detail-table.js +45 -0
- package/dist/examples/simple-detail-table.js.map +1 -0
- package/dist/examples/simple-graph.d.ts +2 -0
- package/dist/examples/simple-graph.d.ts.map +1 -0
- package/dist/examples/simple-graph.js +32 -0
- package/dist/examples/simple-graph.js.map +1 -0
- package/dist/examples/simple-table-wrap.d.ts +2 -0
- package/dist/examples/simple-table-wrap.d.ts.map +1 -0
- package/dist/examples/simple-table-wrap.js +37 -0
- package/dist/examples/simple-table-wrap.js.map +1 -0
- package/dist/examples/table-edge-cases.d.ts +2 -0
- package/dist/examples/table-edge-cases.d.ts.map +1 -0
- package/dist/examples/table-edge-cases.js +70 -0
- package/dist/examples/table-edge-cases.js.map +1 -0
- package/dist/examples/table-flex-grow.d.ts +2 -0
- package/dist/examples/table-flex-grow.d.ts.map +1 -0
- package/dist/examples/table-flex-grow.js +18 -0
- package/dist/examples/table-flex-grow.js.map +1 -0
- package/dist/extensions/dev.d.ts.map +1 -1
- package/dist/extensions/dev.js +5 -1
- package/dist/extensions/dev.js.map +1 -1
- package/dist/globals.d.ts +1 -0
- package/dist/globals.d.ts.map +1 -1
- package/dist/globals.js +2 -0
- package/dist/globals.js.map +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/date-picker-widget.d.ts.map +1 -1
- package/dist/internal/date-picker-widget.js +4 -0
- package/dist/internal/date-picker-widget.js.map +1 -1
- package/dist/internal/providers.d.ts.map +1 -1
- package/dist/internal/providers.js +1 -3
- package/dist/internal/providers.js.map +1 -1
- package/dist/markdown-utils.d.ts +22 -1
- package/dist/markdown-utils.d.ts.map +1 -1
- package/dist/markdown-utils.js +66 -1
- package/dist/markdown-utils.js.map +1 -1
- package/dist/opentui.d.ts +4 -0
- package/dist/opentui.d.ts.map +1 -0
- package/dist/opentui.js +3 -0
- package/dist/opentui.js.map +1 -0
- package/dist/release.d.ts +2 -1
- package/dist/release.d.ts.map +1 -1
- package/dist/release.js +2 -1
- package/dist/release.js.map +1 -1
- package/dist/state.d.ts +1 -0
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +1 -1
- package/dist/state.js.map +1 -1
- package/dist/theme.d.ts +1 -0
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +13 -0
- package/dist/theme.js.map +1 -1
- package/dist/themes/nerv.json +227 -0
- package/dist/themes/termcast.json +72 -71
- package/dist/themes.d.ts +2 -1
- package/dist/themes.d.ts.map +1 -1
- package/dist/themes.js +7 -5
- package/dist/themes.js.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +3 -0
- package/dist/utils.js.map +1 -1
- package/package.json +13 -5
- package/src/build.tsx +13 -0
- package/src/cli.tsx +5 -49
- package/src/colors.tsx +7 -7
- package/src/compile.tsx +52 -29
- package/src/components/actions.tsx +1 -1
- package/src/components/bar-chart.tsx +271 -0
- package/src/components/bar-graph.tsx +214 -0
- package/src/components/detail.tsx +7 -8
- package/src/components/footer.tsx +14 -15
- package/src/components/form/date-picker.tsx +9 -0
- package/src/components/form/dropdown.tsx +13 -3
- package/src/components/form/index.tsx +4 -6
- package/src/components/form/use-form-navigation.tsx +6 -0
- package/src/components/graph.tsx +506 -0
- package/src/components/icon.tsx +5 -5
- package/src/components/list.tsx +210 -102
- package/src/components/loading-bar.tsx +3 -3
- package/src/components/loading-text.tsx +4 -2
- package/src/components/metadata.tsx +2 -2
- package/src/components/row.tsx +31 -0
- package/src/components/table.tsx +511 -0
- package/src/descendants.tsx +13 -13
- package/src/examples/action-shortcut.vitest.tsx +1 -1
- package/src/examples/actions-context.vitest.tsx +1 -1
- package/src/examples/bar-graph-weekly.tsx +264 -0
- package/src/examples/bar-graph-weekly.vitest.tsx +275 -0
- package/src/examples/detail-metadata-showcase.vitest.tsx +8 -8
- package/src/examples/form-basic.vitest.tsx +239 -0
- package/src/examples/form-dropdown.vitest.tsx +29 -29
- package/src/examples/form-tagpicker.vitest.tsx +27 -27
- package/src/examples/github.vitest.tsx +4 -4
- package/src/examples/graph-bar-chart.tsx +408 -0
- package/src/examples/graph-bar-chart.vitest.tsx +283 -0
- package/src/examples/graph-multi-series.tsx +36 -0
- package/src/examples/graph-multi-series.vitest.tsx +89 -0
- package/src/examples/graph-polymarket.tsx +182 -0
- package/src/examples/graph-polymarket.vitest.tsx +130 -0
- package/src/examples/graph-row.tsx +347 -0
- package/src/examples/graph-row.vitest.tsx +295 -0
- package/src/examples/graph-styles.tsx +457 -0
- package/src/examples/graph-styles.vitest.tsx +322 -0
- package/src/examples/list-accessory-table.tsx +77 -0
- package/src/examples/list-detail-metadata.vitest.tsx +21 -21
- package/src/examples/list-dropdown-default.vitest.tsx +12 -12
- package/src/examples/list-item-accessories.tsx +106 -0
- package/src/examples/list-item-accessories.vitest.tsx +115 -0
- package/src/examples/list-no-actions.tsx +18 -0
- package/src/examples/list-no-actions.vitest.tsx +97 -0
- package/src/examples/list-spacing-mode.vitest.tsx +6 -6
- package/src/examples/list-with-detail.vitest.tsx +92 -92
- package/src/examples/list-with-dropdown.vitest.tsx +49 -6
- package/src/examples/list-with-sections.vitest.tsx +61 -56
- package/src/examples/simple-detail-markdown.vitest.tsx +21 -17
- package/src/examples/simple-detail-table.tsx +65 -0
- package/src/examples/simple-detail-table.vitest.tsx +200 -0
- package/src/examples/simple-graph.tsx +51 -0
- package/src/examples/simple-graph.vitest.tsx +124 -0
- package/src/examples/simple-grid.vitest.tsx +3 -3
- package/src/examples/simple-list-search.vitest.tsx +65 -0
- package/src/examples/simple-navigation.vitest.tsx +3 -3
- package/src/examples/simple-table-wrap.tsx +55 -0
- package/src/examples/simple-table-wrap.vitest.tsx +91 -0
- package/src/examples/store.vitest.tsx +1 -1
- package/src/examples/table-edge-cases.tsx +72 -0
- package/src/examples/table-edge-cases.vitest.tsx +307 -0
- package/src/examples/table-flex-grow.tsx +53 -0
- package/src/examples/table-flex-grow.vitest.tsx +124 -0
- package/src/extensions/dev.tsx +7 -1
- package/src/globals.ts +3 -0
- package/src/index.tsx +31 -0
- package/src/internal/date-picker-widget.tsx +4 -0
- package/src/internal/providers.tsx +1 -4
- package/src/markdown-utils.tsx +82 -1
- package/src/opentui.tsx +5 -0
- package/src/release.tsx +3 -0
- package/src/state.tsx +2 -1
- package/src/theme.tsx +14 -0
- package/src/themes/nerv.json +231 -0
- package/src/themes/termcast.json +75 -71
- package/src/themes.ts +8 -5
- package/src/utils.tsx +4 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BarGraph component for rendering vertical stacked bar charts in the terminal.
|
|
3
|
+
*
|
|
4
|
+
* Pure React/opentui implementation using <box> elements with justifyContent
|
|
5
|
+
* "space-evenly" for bar distribution. Each bar is a column of stacked colored
|
|
6
|
+
* segments sized via flexGrow. Labels sit below each bar, truncated with
|
|
7
|
+
* overflow="hidden" when the bar is narrower than the label text.
|
|
8
|
+
*
|
|
9
|
+
* Legend is a compact row of ■ Title pairs, no border.
|
|
10
|
+
*
|
|
11
|
+
* Color palette (same as Graph and BarChart):
|
|
12
|
+
* accent, info, success, warning, error, secondary, primary (cycles with %)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import React, { ReactNode, useMemo } from 'react'
|
|
16
|
+
import { BoxProps } from '@opentui/react'
|
|
17
|
+
import { useTheme, getThemePalette } from 'termcast/src/theme'
|
|
18
|
+
import { Color, resolveColor } from 'termcast/src/colors'
|
|
19
|
+
|
|
20
|
+
// ── Types ────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
export interface BarGraphSeriesProps {
|
|
23
|
+
/** One value per bar position */
|
|
24
|
+
data: number[]
|
|
25
|
+
/** Override the auto-assigned color */
|
|
26
|
+
color?: Color.ColorLike
|
|
27
|
+
/** Series label shown in legend */
|
|
28
|
+
title?: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface BarGraphProps extends BoxProps {
|
|
32
|
+
/** Height of the bar area in terminal rows (default: 15) */
|
|
33
|
+
height?: number
|
|
34
|
+
/** X-axis labels, one per bar position */
|
|
35
|
+
labels?: string[]
|
|
36
|
+
/** Show compact legend below the chart (default: true when any series has a title) */
|
|
37
|
+
showLegend?: boolean
|
|
38
|
+
/** BarGraph.Series children */
|
|
39
|
+
children: ReactNode
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface BarGraphType {
|
|
43
|
+
(props: BarGraphProps): any
|
|
44
|
+
Series: (props: BarGraphSeriesProps) => any
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ── Internal types ───────────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
interface CollectedSeries {
|
|
50
|
+
data: number[]
|
|
51
|
+
color: string
|
|
52
|
+
title?: string
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ── BarGraph.Series (data-only, renders null like Graph.Line) ────────
|
|
56
|
+
|
|
57
|
+
const BarGraphSeries = (_props: BarGraphSeriesProps): any => {
|
|
58
|
+
return null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ── Main BarGraph component ──────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
const BarGraph: BarGraphType = (props) => {
|
|
64
|
+
const theme = useTheme()
|
|
65
|
+
const { height = 15, labels = [], showLegend, children, ...rest } = props
|
|
66
|
+
|
|
67
|
+
const palette = getThemePalette(theme)
|
|
68
|
+
|
|
69
|
+
// Collect series from children
|
|
70
|
+
const seriesList = useMemo<CollectedSeries[]>(() => {
|
|
71
|
+
const result: CollectedSeries[] = []
|
|
72
|
+
let colorIndex = 0
|
|
73
|
+
React.Children.forEach(children, (child) => {
|
|
74
|
+
if (!React.isValidElement(child)) {
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
const childProps = child.props as BarGraphSeriesProps
|
|
78
|
+
if (!childProps.data) {
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
const color = resolveColor(childProps.color) || palette[colorIndex % palette.length]!
|
|
82
|
+
result.push({
|
|
83
|
+
data: childProps.data,
|
|
84
|
+
color,
|
|
85
|
+
title: childProps.title,
|
|
86
|
+
})
|
|
87
|
+
colorIndex++
|
|
88
|
+
})
|
|
89
|
+
return result
|
|
90
|
+
}, [children, palette])
|
|
91
|
+
|
|
92
|
+
// Compute number of bars from max data length across series
|
|
93
|
+
const numBars = useMemo(() => {
|
|
94
|
+
return Math.max(0, ...seriesList.map((s) => s.data.length))
|
|
95
|
+
}, [seriesList])
|
|
96
|
+
|
|
97
|
+
// Compute stacked totals per bar position and the global max
|
|
98
|
+
const { stackedTotals, maxTotal } = useMemo(() => {
|
|
99
|
+
const totals: number[] = []
|
|
100
|
+
for (let i = 0; i < numBars; i++) {
|
|
101
|
+
let sum = 0
|
|
102
|
+
for (const series of seriesList) {
|
|
103
|
+
sum += series.data[i] || 0
|
|
104
|
+
}
|
|
105
|
+
totals.push(sum)
|
|
106
|
+
}
|
|
107
|
+
const max = Math.max(0, ...totals)
|
|
108
|
+
return { stackedTotals: totals, maxTotal: max }
|
|
109
|
+
}, [seriesList, numBars])
|
|
110
|
+
|
|
111
|
+
// Whether to show legend: explicit prop, or auto when any series has a title
|
|
112
|
+
const legendVisible = showLegend ?? seriesList.some((s) => s.title)
|
|
113
|
+
|
|
114
|
+
if (numBars === 0 || maxTotal === 0) {
|
|
115
|
+
return null
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<box flexDirection="column" {...rest}>
|
|
120
|
+
{/* Bars area: overflow="hidden" clips excess bars when there are too many.
|
|
121
|
+
alignItems="flex-start" left-aligns bars in wide containers. */}
|
|
122
|
+
<box flexDirection="row" height={height} width="100%" alignItems="flex-start" overflow="hidden">
|
|
123
|
+
{Array.from({ length: numBars }, (_, barIdx) => {
|
|
124
|
+
const barTotal = stackedTotals[barIdx]!
|
|
125
|
+
const emptyGrow = maxTotal - barTotal
|
|
126
|
+
const label = labels[barIdx]
|
|
127
|
+
|
|
128
|
+
const barElements: any[] = []
|
|
129
|
+
|
|
130
|
+
// Min 1-col gap between bars (not before the first bar)
|
|
131
|
+
if (barIdx > 0) {
|
|
132
|
+
barElements.push(
|
|
133
|
+
<box key={`gap-${barIdx}`} width={1} flexShrink={0} />
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
barElements.push(
|
|
138
|
+
<box
|
|
139
|
+
key={barIdx}
|
|
140
|
+
flexDirection="column"
|
|
141
|
+
height="100%"
|
|
142
|
+
flexGrow={0}
|
|
143
|
+
flexShrink={0}
|
|
144
|
+
width={3}
|
|
145
|
+
>
|
|
146
|
+
{/* Plot area: spacer on top pushes colored segments to the bottom
|
|
147
|
+
so all bars are bottom-aligned regardless of total value. */}
|
|
148
|
+
<box flexDirection="column" flexGrow={1} width="100%">
|
|
149
|
+
{emptyGrow > 0 && (
|
|
150
|
+
<box flexGrow={emptyGrow} />
|
|
151
|
+
)}
|
|
152
|
+
{/* Segments: last series at top, first at bottom.
|
|
153
|
+
Each segment uses backgroundColor for the visual fill, plus
|
|
154
|
+
a single █ with matching fg so bars appear in text snapshots.
|
|
155
|
+
wrapMode="none" prevents text from expanding the segment height. */}
|
|
156
|
+
{[...seriesList].reverse().map((series, reverseIdx) => {
|
|
157
|
+
const value = series.data[barIdx] || 0
|
|
158
|
+
if (value <= 0) {
|
|
159
|
+
return null
|
|
160
|
+
}
|
|
161
|
+
return (
|
|
162
|
+
<box
|
|
163
|
+
key={reverseIdx}
|
|
164
|
+
flexGrow={value}
|
|
165
|
+
backgroundColor={series.color}
|
|
166
|
+
width="100%"
|
|
167
|
+
minHeight={1}
|
|
168
|
+
overflow="hidden"
|
|
169
|
+
>
|
|
170
|
+
{/* Absolute-positioned text doesn't affect flex layout.
|
|
171
|
+
The parent height is purely from flexGrow. The text
|
|
172
|
+
wraps to fill the area and gets clipped. */}
|
|
173
|
+
<box position="absolute" width="100%" height="100%" overflow="hidden">
|
|
174
|
+
<text fg={series.color}>{'█'.repeat(200)}</text>
|
|
175
|
+
</box>
|
|
176
|
+
</box>
|
|
177
|
+
)
|
|
178
|
+
})}
|
|
179
|
+
</box>
|
|
180
|
+
{/* X-axis label */}
|
|
181
|
+
{label !== undefined && (
|
|
182
|
+
<box height={1} width="100%" overflow="hidden" flexShrink={0}>
|
|
183
|
+
<text wrapMode="none" fg={theme.textMuted}>{label}</text>
|
|
184
|
+
</box>
|
|
185
|
+
)}
|
|
186
|
+
</box>
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
return barElements
|
|
190
|
+
})}
|
|
191
|
+
</box>
|
|
192
|
+
{/* Legend: single line, no wrap, clips when too many series */}
|
|
193
|
+
{legendVisible && (
|
|
194
|
+
<box height={1} width="100%" flexShrink={0} overflow="hidden">
|
|
195
|
+
<text wrapMode="none">
|
|
196
|
+
{seriesList.filter((s) => s.title).map((series, i, arr) => {
|
|
197
|
+
const sep = i < arr.length - 1 ? ' ' : ''
|
|
198
|
+
return (
|
|
199
|
+
<React.Fragment key={i}>
|
|
200
|
+
<span fg={series.color}>■ </span>
|
|
201
|
+
<span fg={theme.textMuted}>{series.title}{sep}</span>
|
|
202
|
+
</React.Fragment>
|
|
203
|
+
)
|
|
204
|
+
})}
|
|
205
|
+
</text>
|
|
206
|
+
</box>
|
|
207
|
+
)}
|
|
208
|
+
</box>
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
BarGraph.Series = BarGraphSeries
|
|
213
|
+
|
|
214
|
+
export { BarGraph }
|
|
@@ -72,7 +72,7 @@ const DetailMetadata: DetailMetadataType = (props) => {
|
|
|
72
72
|
const config: MetadataConfig = {
|
|
73
73
|
maxValueLen: 9999, // No limit - let text wrap naturally
|
|
74
74
|
titleMinWidth: computedTitleWidth,
|
|
75
|
-
paddingBottom:
|
|
75
|
+
paddingBottom: 0,
|
|
76
76
|
// Keep separators deterministic by rendering a fixed number of chars.
|
|
77
77
|
// The content area has paddingRight=2 in <Detail>, so leave some margin.
|
|
78
78
|
separatorWidth: Math.max(20, width - 6),
|
|
@@ -81,6 +81,7 @@ const DetailMetadata: DetailMetadataType = (props) => {
|
|
|
81
81
|
return (
|
|
82
82
|
<MetadataContext.Provider value={config}>
|
|
83
83
|
<box
|
|
84
|
+
gap={1}
|
|
84
85
|
style={{
|
|
85
86
|
flexDirection: 'column',
|
|
86
87
|
paddingTop: 1,
|
|
@@ -195,10 +196,8 @@ const Detail: DetailType = (props) => {
|
|
|
195
196
|
if (!inFocus) return
|
|
196
197
|
|
|
197
198
|
if (evt.name === 'k' && evt.ctrl) {
|
|
198
|
-
//
|
|
199
|
-
|
|
200
|
-
useStore.setState({ showActionsDialog: true })
|
|
201
|
-
}
|
|
199
|
+
// Always open — built-in actions (Change Theme, etc.) are always available
|
|
200
|
+
useStore.setState({ showActionsDialog: true })
|
|
202
201
|
} else if (evt.name === 'return' && actions) {
|
|
203
202
|
// Enter auto-executes first action via ActionPanel's layout effect
|
|
204
203
|
useStore.setState({ shouldAutoExecuteFirstAction: true })
|
|
@@ -236,11 +235,11 @@ const Detail: DetailType = (props) => {
|
|
|
236
235
|
<box style={{ flexDirection: 'column', height: '100%', flexGrow: 1 }}>
|
|
237
236
|
{content}
|
|
238
237
|
<DetailFooter
|
|
239
|
-
hasActions={
|
|
238
|
+
hasActions={true}
|
|
240
239
|
firstActionTitle={firstActionTitle}
|
|
241
240
|
/>
|
|
242
|
-
{/*
|
|
243
|
-
{actions
|
|
241
|
+
{/* Always mount ActionPanel offscreen so built-in actions are available */}
|
|
242
|
+
<Offscreen>{actions || <ActionPanel />}</Offscreen>
|
|
244
243
|
</box>
|
|
245
244
|
)
|
|
246
245
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { ReactNode, useState, useEffect } from 'react'
|
|
2
2
|
import { TextAttributes } from '@opentui/core'
|
|
3
3
|
import { useTerminalDimensions, useKeyboard } from '@opentui/react'
|
|
4
|
-
|
|
4
|
+
|
|
5
5
|
import { useTheme } from 'termcast/src/theme'
|
|
6
6
|
import { openInBrowser } from 'termcast/src/action-utils'
|
|
7
7
|
import { termcastMaxContentWidth } from 'termcast/src/utils'
|
|
@@ -88,7 +88,8 @@ function ToastInline({ toast }: { toast: ToastData }): any {
|
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
// All toast styles use a colored background with contrasting text
|
|
92
|
+
const toastBg = (() => {
|
|
92
93
|
switch (toast.style) {
|
|
93
94
|
case 'SUCCESS':
|
|
94
95
|
return theme.success
|
|
@@ -99,10 +100,8 @@ function ToastInline({ toast }: { toast: ToastData }): any {
|
|
|
99
100
|
default:
|
|
100
101
|
return theme.success
|
|
101
102
|
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const primaryColor = theme.primary
|
|
105
|
-
const mutedColor = colord(primaryColor).darken(0.06).toHex()
|
|
103
|
+
})()
|
|
104
|
+
const toastFg = theme.background
|
|
106
105
|
|
|
107
106
|
const maxToastWidth = Math.min(terminalWidth, termcastMaxContentWidth)
|
|
108
107
|
const toastWidth = maxToastWidth - TOAST_MARGIN * 2
|
|
@@ -117,6 +116,7 @@ function ToastInline({ toast }: { toast: ToastData }): any {
|
|
|
117
116
|
flexShrink={0}
|
|
118
117
|
overflow='hidden'
|
|
119
118
|
height={1}
|
|
119
|
+
backgroundColor={toastBg}
|
|
120
120
|
>
|
|
121
121
|
{/* Title box */}
|
|
122
122
|
<box
|
|
@@ -126,12 +126,12 @@ function ToastInline({ toast }: { toast: ToastData }): any {
|
|
|
126
126
|
overflow='hidden'
|
|
127
127
|
height={1}
|
|
128
128
|
>
|
|
129
|
-
<text flexShrink={0} fg={
|
|
129
|
+
<text flexShrink={0} fg={toastFg} wrapMode='none'>
|
|
130
130
|
{getIcon()}{' '}
|
|
131
131
|
</text>
|
|
132
132
|
<text
|
|
133
133
|
flexShrink={1}
|
|
134
|
-
fg={
|
|
134
|
+
fg={toastFg}
|
|
135
135
|
attributes={TextAttributes.BOLD}
|
|
136
136
|
wrapMode='none'
|
|
137
137
|
>
|
|
@@ -148,14 +148,13 @@ function ToastInline({ toast }: { toast: ToastData }): any {
|
|
|
148
148
|
overflow='hidden'
|
|
149
149
|
height={1}
|
|
150
150
|
>
|
|
151
|
-
<text fg={
|
|
151
|
+
<text fg={toastFg} flexShrink={1} wrapMode='none'>
|
|
152
152
|
{toast.message || ''}
|
|
153
153
|
</text>
|
|
154
154
|
</box>
|
|
155
155
|
{/* Keys box (right aligned, no grow) */}
|
|
156
156
|
<box
|
|
157
157
|
paddingLeft={1}
|
|
158
|
-
|
|
159
158
|
gap={1}
|
|
160
159
|
flexDirection='row'
|
|
161
160
|
flexShrink={0}
|
|
@@ -171,13 +170,13 @@ function ToastInline({ toast }: { toast: ToastData }): any {
|
|
|
171
170
|
}}
|
|
172
171
|
>
|
|
173
172
|
<text
|
|
174
|
-
fg={
|
|
173
|
+
fg={toastFg}
|
|
175
174
|
attributes={TextAttributes.BOLD}
|
|
176
175
|
wrapMode='none'
|
|
177
176
|
>
|
|
178
177
|
{toast.primaryAction.title}
|
|
179
178
|
</text>
|
|
180
|
-
<text fg={
|
|
179
|
+
<text fg={toastFg} wrapMode='none'>
|
|
181
180
|
{' '}
|
|
182
181
|
ctrl t
|
|
183
182
|
</text>
|
|
@@ -192,13 +191,13 @@ function ToastInline({ toast }: { toast: ToastData }): any {
|
|
|
192
191
|
}}
|
|
193
192
|
>
|
|
194
193
|
<text
|
|
195
|
-
fg={
|
|
194
|
+
fg={toastFg}
|
|
196
195
|
attributes={TextAttributes.BOLD}
|
|
197
196
|
wrapMode='none'
|
|
198
197
|
>
|
|
199
198
|
{toast.secondaryAction.title}
|
|
200
199
|
</text>
|
|
201
|
-
<text fg={
|
|
200
|
+
<text fg={toastFg} wrapMode='none'>
|
|
202
201
|
{' '}
|
|
203
202
|
ctrl g
|
|
204
203
|
</text>
|
|
@@ -257,7 +256,7 @@ export function Footer({
|
|
|
257
256
|
fg={theme.textMuted}
|
|
258
257
|
attributes={TextAttributes.BOLD}
|
|
259
258
|
>
|
|
260
|
-
termcast
|
|
259
|
+
termcast.app
|
|
261
260
|
</text>
|
|
262
261
|
</box>
|
|
263
262
|
)}
|
|
@@ -34,6 +34,9 @@ const DatePickerComponent = (props: DatePickerProps): any => {
|
|
|
34
34
|
const { control } = useFormContext()
|
|
35
35
|
const focusContext = useFocusContext()
|
|
36
36
|
const { focusedField, setFocusedField } = focusContext
|
|
37
|
+
const { navigateToPrevious, navigateToNext } = useFormNavigationHelpers(
|
|
38
|
+
props.id,
|
|
39
|
+
)
|
|
37
40
|
const isFocused = focusedField === props.id
|
|
38
41
|
const isInFocus = useIsInFocus()
|
|
39
42
|
|
|
@@ -71,6 +74,12 @@ const DatePickerComponent = (props: DatePickerProps): any => {
|
|
|
71
74
|
<DatePickerWidget
|
|
72
75
|
enableColors={isFocused}
|
|
73
76
|
initialValue={field.value || undefined}
|
|
77
|
+
onFirstRowUpKey={() => {
|
|
78
|
+
navigateToPrevious()
|
|
79
|
+
}}
|
|
80
|
+
onLastRowDownKey={() => {
|
|
81
|
+
navigateToNext()
|
|
82
|
+
}}
|
|
74
83
|
onChange={(date) => {
|
|
75
84
|
field.onChange(date)
|
|
76
85
|
if (props.onChange) {
|
|
@@ -198,6 +198,9 @@ const DropdownContent = ({
|
|
|
198
198
|
const isInFocus = useIsInFocus()
|
|
199
199
|
const focusContext = useFocusContext()
|
|
200
200
|
const { focusedField, setFocusedField } = focusContext
|
|
201
|
+
const { navigateToPrevious, navigateToNext } = useFormNavigationHelpers(
|
|
202
|
+
props.id,
|
|
203
|
+
)
|
|
201
204
|
const isFocused = focusedField === props.id
|
|
202
205
|
const [focusedIndex, setFocusedIndex] = useState(0)
|
|
203
206
|
|
|
@@ -295,7 +298,6 @@ const DropdownContent = ({
|
|
|
295
298
|
// Handle keyboard navigation when focused
|
|
296
299
|
useKeyboard((evt) => {
|
|
297
300
|
if (!isFocused || !isInFocus) return
|
|
298
|
-
|
|
299
301
|
const items = Object.values(descendantsContext.committedMap)
|
|
300
302
|
.filter((item) => item.index !== -1)
|
|
301
303
|
.sort((a, b) => a.index - b.index)
|
|
@@ -303,7 +305,11 @@ const DropdownContent = ({
|
|
|
303
305
|
|
|
304
306
|
if (itemCount > 0) {
|
|
305
307
|
if (evt.name === 'down') {
|
|
306
|
-
if (focusedIndex >= itemCount - 1)
|
|
308
|
+
if (focusedIndex >= itemCount - 1) {
|
|
309
|
+
navigateToNext()
|
|
310
|
+
evt.stopPropagation()
|
|
311
|
+
return
|
|
312
|
+
}
|
|
307
313
|
const nextIndex = focusedIndex + 1
|
|
308
314
|
const nextItem = items[nextIndex]
|
|
309
315
|
if (nextItem) {
|
|
@@ -313,7 +319,11 @@ const DropdownContent = ({
|
|
|
313
319
|
scrollToItemIfNeeded({ item: nextItem, direction: 1 })
|
|
314
320
|
}
|
|
315
321
|
} else if (evt.name === 'up') {
|
|
316
|
-
if (focusedIndex <= 0)
|
|
322
|
+
if (focusedIndex <= 0) {
|
|
323
|
+
navigateToPrevious()
|
|
324
|
+
evt.stopPropagation()
|
|
325
|
+
return
|
|
326
|
+
}
|
|
317
327
|
const nextIndex = focusedIndex - 1
|
|
318
328
|
const nextItem = items[nextIndex]
|
|
319
329
|
if (nextItem) {
|
|
@@ -331,10 +331,8 @@ export const Form: FormType = ((props) => {
|
|
|
331
331
|
}
|
|
332
332
|
|
|
333
333
|
if (evt.name === 'k' && evt.ctrl) {
|
|
334
|
-
//
|
|
335
|
-
|
|
336
|
-
useStore.setState({ showActionsDialog: true })
|
|
337
|
-
}
|
|
334
|
+
// Always open — built-in actions (Change Theme, etc.) are always available
|
|
335
|
+
useStore.setState({ showActionsDialog: true })
|
|
338
336
|
} else if ((evt.name === 'return' && evt.ctrl) || (evt.name === 'return' && evt.meta)) {
|
|
339
337
|
// Ctrl+Return or Cmd+Return auto-executes first action via ActionPanel
|
|
340
338
|
if (props.actions) {
|
|
@@ -413,8 +411,8 @@ export const Form: FormType = ((props) => {
|
|
|
413
411
|
</box>
|
|
414
412
|
</ScrollBox>
|
|
415
413
|
<FormFooter />
|
|
416
|
-
{/*
|
|
417
|
-
{props.actions
|
|
414
|
+
{/* Always mount ActionPanel offscreen so built-in actions are available */}
|
|
415
|
+
<Offscreen>{props.actions || <ActionPanel />}</Offscreen>
|
|
418
416
|
</box>
|
|
419
417
|
</box>
|
|
420
418
|
</FocusContext.Provider>
|
|
@@ -68,11 +68,17 @@ export function useFormNavigation(
|
|
|
68
68
|
} else {
|
|
69
69
|
navigateToNext()
|
|
70
70
|
}
|
|
71
|
+
evt.stopPropagation()
|
|
71
72
|
} else if (handleArrows) {
|
|
73
|
+
// Prevent the newly-focused field from also processing this arrow.
|
|
74
|
+
// setFocusedField uses flushSync which updates all useKeyboard handler
|
|
75
|
+
// refs via useEffectEvent before the next handler in the dispatch loop runs.
|
|
72
76
|
if (evt.name === 'up') {
|
|
73
77
|
navigateToPrevious()
|
|
78
|
+
evt.stopPropagation()
|
|
74
79
|
} else if (evt.name === 'down') {
|
|
75
80
|
navigateToNext()
|
|
81
|
+
evt.stopPropagation()
|
|
76
82
|
}
|
|
77
83
|
}
|
|
78
84
|
})
|