termcast 1.4.1 → 1.5.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.
Files changed (140) hide show
  1. package/dist/build.d.ts.map +1 -1
  2. package/dist/build.js +8 -7
  3. package/dist/build.js.map +1 -1
  4. package/dist/cli.js +0 -40
  5. package/dist/cli.js.map +1 -1
  6. package/dist/components/bar-graph.d.ts +23 -8
  7. package/dist/components/bar-graph.d.ts.map +1 -1
  8. package/dist/components/bar-graph.js +84 -40
  9. package/dist/components/bar-graph.js.map +1 -1
  10. package/dist/components/dotted-line-graph.d.ts +86 -0
  11. package/dist/components/dotted-line-graph.d.ts.map +1 -0
  12. package/dist/components/dotted-line-graph.js +260 -0
  13. package/dist/components/dotted-line-graph.js.map +1 -0
  14. package/dist/components/extension-preferences.d.ts.map +1 -1
  15. package/dist/components/extension-preferences.js +1 -10
  16. package/dist/components/extension-preferences.js.map +1 -1
  17. package/dist/components/graph.d.ts.map +1 -1
  18. package/dist/components/graph.js +7 -1
  19. package/dist/components/graph.js.map +1 -1
  20. package/dist/components/histogram.d.ts +42 -0
  21. package/dist/components/histogram.d.ts.map +1 -0
  22. package/dist/components/histogram.js +115 -0
  23. package/dist/components/histogram.js.map +1 -0
  24. package/dist/components/horizontal-bar-graph.d.ts +47 -0
  25. package/dist/components/horizontal-bar-graph.d.ts.map +1 -0
  26. package/dist/components/horizontal-bar-graph.js +137 -0
  27. package/dist/components/horizontal-bar-graph.js.map +1 -0
  28. package/dist/components/list.d.ts +2 -0
  29. package/dist/components/list.d.ts.map +1 -1
  30. package/dist/components/list.js +10 -10
  31. package/dist/components/list.js.map +1 -1
  32. package/dist/examples/bar-graph-weekly.js +2 -2
  33. package/dist/examples/bar-graph-weekly.js.map +1 -1
  34. package/dist/examples/charts-showcase-barchart.d.ts +2 -0
  35. package/dist/examples/charts-showcase-barchart.d.ts.map +1 -0
  36. package/dist/examples/charts-showcase-barchart.js +10 -0
  37. package/dist/examples/charts-showcase-barchart.js.map +1 -0
  38. package/dist/examples/charts-showcase-bargraph.d.ts +2 -0
  39. package/dist/examples/charts-showcase-bargraph.d.ts.map +1 -0
  40. package/dist/examples/charts-showcase-bargraph.js +60 -0
  41. package/dist/examples/charts-showcase-bargraph.js.map +1 -0
  42. package/dist/examples/charts-showcase-candle.d.ts +2 -0
  43. package/dist/examples/charts-showcase-candle.d.ts.map +1 -0
  44. package/dist/examples/charts-showcase-candle.js +30 -0
  45. package/dist/examples/charts-showcase-candle.js.map +1 -0
  46. package/dist/examples/charts-showcase-graph.d.ts +2 -0
  47. package/dist/examples/charts-showcase-graph.d.ts.map +1 -0
  48. package/dist/examples/charts-showcase-graph.js +33 -0
  49. package/dist/examples/charts-showcase-graph.js.map +1 -0
  50. package/dist/examples/charts-showcase-heatmap.d.ts +2 -0
  51. package/dist/examples/charts-showcase-heatmap.d.ts.map +1 -0
  52. package/dist/examples/charts-showcase-heatmap.js +36 -0
  53. package/dist/examples/charts-showcase-heatmap.js.map +1 -0
  54. package/dist/examples/charts-showcase-mixed.d.ts +2 -0
  55. package/dist/examples/charts-showcase-mixed.d.ts.map +1 -0
  56. package/dist/examples/charts-showcase-mixed.js +30 -0
  57. package/dist/examples/charts-showcase-mixed.js.map +1 -0
  58. package/dist/examples/charts-showcase-progress.d.ts +2 -0
  59. package/dist/examples/charts-showcase-progress.d.ts.map +1 -0
  60. package/dist/examples/charts-showcase-progress.js +10 -0
  61. package/dist/examples/charts-showcase-progress.js.map +1 -0
  62. package/dist/examples/graph-multi-series.js +1 -1
  63. package/dist/examples/graph-multi-series.js.map +1 -1
  64. package/dist/examples/horizontal-bar-graph-weekly.d.ts +2 -0
  65. package/dist/examples/horizontal-bar-graph-weekly.d.ts.map +1 -0
  66. package/dist/examples/horizontal-bar-graph-weekly.js +67 -0
  67. package/dist/examples/horizontal-bar-graph-weekly.js.map +1 -0
  68. package/dist/examples/simple-dotted-line-graph.d.ts +2 -0
  69. package/dist/examples/simple-dotted-line-graph.d.ts.map +1 -0
  70. package/dist/examples/simple-dotted-line-graph.js +39 -0
  71. package/dist/examples/simple-dotted-line-graph.js.map +1 -0
  72. package/dist/examples/simple-histogram.d.ts +2 -0
  73. package/dist/examples/simple-histogram.d.ts.map +1 -0
  74. package/dist/examples/simple-histogram.js +47 -0
  75. package/dist/examples/simple-histogram.js.map +1 -0
  76. package/dist/index.d.ts +6 -0
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +6 -0
  79. package/dist/index.js.map +1 -1
  80. package/dist/platform/node/sqlite.d.ts +6 -5
  81. package/dist/platform/node/sqlite.d.ts.map +1 -1
  82. package/dist/platform/node/sqlite.js +30 -14
  83. package/dist/platform/node/sqlite.js.map +1 -1
  84. package/dist/theme.d.ts.map +1 -1
  85. package/dist/theme.js +11 -9
  86. package/dist/theme.js.map +1 -1
  87. package/dist/utils/run-command.d.ts.map +1 -1
  88. package/dist/utils/run-command.js +8 -19
  89. package/dist/utils/run-command.js.map +1 -1
  90. package/dist/utils.d.ts +1 -19
  91. package/dist/utils.d.ts.map +1 -1
  92. package/dist/utils.js +1 -100
  93. package/dist/utils.js.map +1 -1
  94. package/package.json +6 -8
  95. package/src/build.tsx +11 -10
  96. package/src/cli.tsx +3 -40
  97. package/src/compile.vitest.tsx +3 -3
  98. package/src/components/bar-graph.tsx +217 -111
  99. package/src/components/dotted-line-graph.tsx +407 -0
  100. package/src/components/extension-preferences.tsx +2 -12
  101. package/src/components/graph.tsx +5 -1
  102. package/src/components/histogram.tsx +228 -0
  103. package/src/components/horizontal-bar-graph.tsx +279 -0
  104. package/src/components/list.tsx +20 -15
  105. package/src/examples/action-shortcut.vitest.tsx +17 -17
  106. package/src/examples/bar-graph-weekly.tsx +2 -2
  107. package/src/examples/bar-graph-weekly.vitest.tsx +63 -62
  108. package/src/examples/charts-showcase-bargraph.tsx +103 -0
  109. package/src/examples/detail-metadata-showcase.vitest.tsx +12 -12
  110. package/src/examples/form-basic.vitest.tsx +11 -11
  111. package/src/examples/form-dropdown.vitest.tsx +11 -11
  112. package/src/examples/form-scroll.vitest.tsx +1 -1
  113. package/src/examples/form-tagpicker.vitest.tsx +11 -11
  114. package/src/examples/github.vitest.tsx +22 -22
  115. package/src/examples/graph-bar-chart.vitest.tsx +8 -8
  116. package/src/examples/graph-multi-series.tsx +1 -1
  117. package/src/examples/graph-row.vitest.tsx +14 -14
  118. package/src/examples/graph-styles.vitest.tsx +77 -77
  119. package/src/examples/horizontal-bar-graph-weekly.tsx +138 -0
  120. package/src/examples/horizontal-bar-graph-weekly.vitest.tsx +164 -0
  121. package/src/examples/list-detail-metadata.vitest.tsx +4 -4
  122. package/src/examples/list-with-detail.vitest.tsx +46 -46
  123. package/src/examples/simple-candle-chart.vitest.tsx +8 -8
  124. package/src/examples/simple-dotted-line-graph.tsx +53 -0
  125. package/src/examples/simple-dotted-line-graph.vitest.tsx +62 -0
  126. package/src/examples/simple-grid.vitest.tsx +4 -4
  127. package/src/examples/simple-heatmap.vitest.tsx +9 -9
  128. package/src/examples/simple-histogram.tsx +90 -0
  129. package/src/examples/simple-navigation.vitest.tsx +4 -4
  130. package/src/examples/swift-extension.vitest.tsx +3 -3
  131. package/src/extensions/dev.vitest.tsx +8 -8
  132. package/src/index.tsx +21 -0
  133. package/src/platform/node/sqlite.ts +29 -13
  134. package/src/theme.tsx +11 -10
  135. package/src/utils/run-command.tsx +10 -19
  136. package/src/utils.tsx +0 -163
  137. package/src/examples/store.tsx +0 -4
  138. package/src/examples/store.vitest.tsx +0 -78
  139. package/src/extensions/home.tsx +0 -227
  140. package/src/extensions/store.tsx +0 -375
@@ -0,0 +1,279 @@
1
+ /**
2
+ * HorizontalBarGraph renders multi-series horizontal stacked bars.
3
+ *
4
+ * Each row is one category, usually a time bucket. The left chart area grows to
5
+ * fill available space while the right legend uses only the width needed for
6
+ * colored series rows and percentages.
7
+ */
8
+
9
+ import React, { ReactNode, useMemo } from 'react'
10
+ import { BoxProps } from '@opentui/react'
11
+ import { Color, resolveColor } from 'termcast/src/colors'
12
+ import { getThemePalette, useTheme } from 'termcast/src/theme'
13
+
14
+ export interface HorizontalBarGraphSeriesProps {
15
+ /** One value per row/category position. */
16
+ data: number[]
17
+ /** Series label shown in the legend. */
18
+ title?: string
19
+ /** Override the auto-assigned color. */
20
+ color?: Color.ColorLike
21
+ }
22
+
23
+ export interface HorizontalBarGraphProps extends BoxProps {
24
+ /** Row labels, one per bar position. Usually time buckets. */
25
+ labels?: string[]
26
+ /** Maximum chart rows to render. Defaults to all rows. */
27
+ height?: number
28
+ /** Character used for bar cells. Matches Histogram by default. */
29
+ barCharacter?: string
30
+ /** Show header row. Default true. */
31
+ showHeader?: boolean
32
+ /** Show right-side legend. Default true when any series has a title. */
33
+ showLegend?: boolean
34
+ /** Max display width for labels; longer labels are truncated. Default 16. */
35
+ maxLabelWidth?: number
36
+ /** Header text for the category column. Default "category". */
37
+ categoryTitle?: string
38
+ /** Header text for the bar column. Default "distribution". */
39
+ distributionTitle?: string
40
+ /** Header text for the legend column. Default "legend". */
41
+ legendTitle?: string
42
+ /** HorizontalBarGraph.Series children. */
43
+ children: ReactNode
44
+ }
45
+
46
+ interface HorizontalBarGraphType {
47
+ (props: HorizontalBarGraphProps): any
48
+ Series: (props: HorizontalBarGraphSeriesProps) => any
49
+ }
50
+
51
+ function truncateLabel(label: string, maxLabelWidth: number): string {
52
+ if (label.length <= maxLabelWidth) {
53
+ return label
54
+ }
55
+ return label.slice(0, maxLabelWidth - 1) + '…'
56
+ }
57
+
58
+ function padRight(value: string, width: number): string {
59
+ if (value.length >= width) {
60
+ return value
61
+ }
62
+ return value + ' '.repeat(width - value.length)
63
+ }
64
+
65
+ function padLeft(value: string, width: number): string {
66
+ if (value.length >= width) {
67
+ return value
68
+ }
69
+ return ' '.repeat(width - value.length) + value
70
+ }
71
+
72
+ function formatPercentage(value: number, total: number): string {
73
+ if (total <= 0) {
74
+ return '0%'
75
+ }
76
+ return `${Math.round((value / total) * 100)}%`
77
+ }
78
+
79
+ const HorizontalBarGraphSeries = (_props: HorizontalBarGraphSeriesProps): any => {
80
+ return null
81
+ }
82
+
83
+ const HorizontalBarGraph: HorizontalBarGraphType = (props) => {
84
+ const theme = useTheme()
85
+ const {
86
+ labels = [],
87
+ height,
88
+ barCharacter = '╻',
89
+ showHeader = true,
90
+ showLegend,
91
+ maxLabelWidth = 16,
92
+ categoryTitle = 'category',
93
+ distributionTitle = 'distribution',
94
+ legendTitle = 'legend',
95
+ children,
96
+ ...rest
97
+ } = props
98
+
99
+ const palette = getThemePalette(theme)
100
+
101
+ const seriesList = useMemo<Array<{ data: number[]; color: string; title?: string }>>(() => {
102
+ const childArray = React.Children.toArray(children)
103
+ return childArray
104
+ .filter(React.isValidElement)
105
+ .map((child, index) => {
106
+ const childProps = child.props as HorizontalBarGraphSeriesProps
107
+ return {
108
+ data: childProps.data,
109
+ title: childProps.title,
110
+ color: resolveColor(childProps.color) || palette[index % palette.length]!,
111
+ }
112
+ })
113
+ .filter((series) => {
114
+ return Array.isArray(series.data)
115
+ })
116
+ }, [children, palette])
117
+
118
+ const rowCount = useMemo(() => {
119
+ return Math.max(labels.length, ...seriesList.map((series) => series.data.length), 0)
120
+ }, [labels, seriesList])
121
+
122
+ const rows = useMemo<Array<{ label: string; total: number; values: number[] }>>(() => {
123
+ return Array.from({ length: rowCount }, (_, rowIndex) => {
124
+ const values = seriesList.map((series) => {
125
+ return series.data[rowIndex] || 0
126
+ })
127
+ const total = values.reduce((sum, value) => {
128
+ return sum + value
129
+ }, 0)
130
+ return {
131
+ label: labels[rowIndex] ?? String(rowIndex + 1),
132
+ total,
133
+ values,
134
+ }
135
+ })
136
+ }, [labels, rowCount, seriesList])
137
+
138
+ const visibleRows = useMemo(() => {
139
+ return rows.slice(0, height ?? rows.length)
140
+ }, [height, rows])
141
+
142
+ const maxTotal = useMemo(() => {
143
+ return Math.max(0, ...rows.map((row) => row.total))
144
+ }, [rows])
145
+
146
+ const legendRows = useMemo<Array<{ title: string; color: string; percentage: string; total: number }>>(() => {
147
+ const grandTotal = rows.reduce((sum, row) => {
148
+ return sum + row.total
149
+ }, 0)
150
+ return seriesList
151
+ .map((series, seriesIndex) => {
152
+ const seriesTotal = series.data.reduce((sum, value) => {
153
+ return sum + value
154
+ }, 0)
155
+ return {
156
+ title: series.title ?? `Series ${seriesIndex + 1}`,
157
+ color: series.color,
158
+ percentage: formatPercentage(seriesTotal, grandTotal),
159
+ total: seriesTotal,
160
+ }
161
+ })
162
+ .filter((row) => {
163
+ return row.title.length > 0
164
+ })
165
+ .sort((a, b) => {
166
+ return b.total - a.total
167
+ })
168
+ }, [rows, seriesList])
169
+
170
+ const legendVisible = showLegend ?? seriesList.some((series) => {
171
+ return Boolean(series.title)
172
+ })
173
+
174
+ if (seriesList.length === 0 || visibleRows.length === 0 || maxTotal === 0) {
175
+ return null
176
+ }
177
+
178
+ const displayLabels = visibleRows.map((row) => {
179
+ return truncateLabel(row.label, maxLabelWidth)
180
+ })
181
+ const displayCategoryTitle = truncateLabel(categoryTitle, maxLabelWidth)
182
+ const labelWidth = Math.max(5, displayCategoryTitle.length, ...displayLabels.map((label) => label.length))
183
+
184
+ const legendGap = 2
185
+ const legendTitleWidth = Math.max(6, ...legendRows.map((row) => row.title.length))
186
+ const legendPercentageWidth = Math.max(3, ...legendRows.map((row) => row.percentage.length))
187
+ const legendWidth = legendVisible ? legendGap + 2 + legendTitleWidth + 2 + legendPercentageWidth : 0
188
+ const headerHeight = showHeader ? 2 : 0
189
+ const chartHeight = headerHeight + visibleRows.length
190
+
191
+ return (
192
+ <box flexDirection="column" width="100%" flexShrink={0} {...rest}>
193
+ {showHeader && (
194
+ <>
195
+ <box flexDirection="row" height={1} flexShrink={0}>
196
+ <box width={labelWidth + 2} flexShrink={0} overflow="hidden">
197
+ <text fg={theme.textMuted} wrapMode="none">{padRight(displayCategoryTitle, labelWidth)} </text>
198
+ </box>
199
+ <box flexGrow={1} flexShrink={1} overflow="hidden">
200
+ <text fg={theme.textMuted} wrapMode="none">{distributionTitle}</text>
201
+ </box>
202
+ {legendVisible && (
203
+ <box width={legendWidth} flexShrink={0} overflow="hidden">
204
+ <text fg={theme.textMuted} wrapMode="none">{' '.repeat(legendGap)}{legendTitle}</text>
205
+ </box>
206
+ )}
207
+ </box>
208
+ <box flexDirection="row" height={1} flexShrink={0}>
209
+ <box width={labelWidth + 2} flexShrink={0} overflow="hidden">
210
+ <text fg={theme.borderSubtle} wrapMode="none">{'─'.repeat(labelWidth)} </text>
211
+ </box>
212
+ <box flexGrow={1} flexShrink={1} overflow="hidden">
213
+ <text fg={theme.borderSubtle} wrapMode="none">{'─'.repeat(200)}</text>
214
+ </box>
215
+ {legendVisible && (
216
+ <box width={legendWidth} flexShrink={0} overflow="hidden">
217
+ <text fg={theme.borderSubtle} wrapMode="none">{' '.repeat(legendGap)}{'─'.repeat(legendWidth - legendGap)}</text>
218
+ </box>
219
+ )}
220
+ </box>
221
+ </>
222
+ )}
223
+
224
+ <box flexDirection="row" height={chartHeight - headerHeight} flexShrink={0}>
225
+ <box flexDirection="column" flexGrow={1} flexShrink={1} overflow="hidden">
226
+ {visibleRows.map((row, rowIndex) => {
227
+ return (
228
+ <box key={row.label} flexDirection="row" height={1} flexShrink={0} overflow="hidden">
229
+ <box width={labelWidth + 2} flexShrink={0} overflow="hidden">
230
+ <text fg={theme.text} wrapMode="none">
231
+ {padRight(displayLabels[rowIndex]!, labelWidth)}
232
+ </text>
233
+ </box>
234
+ <box flexDirection="row" flexGrow={1} flexShrink={1} overflow="hidden">
235
+ {row.values.map((value, seriesIndex) => {
236
+ if (value <= 0) {
237
+ return null
238
+ }
239
+ const series = seriesList[seriesIndex]!
240
+ return (
241
+ <box key={seriesIndex} flexGrow={value} flexBasis={0} flexShrink={1} overflow="hidden">
242
+ <box position="absolute" width="100%" height="100%" overflow="hidden">
243
+ <text fg={series.color} wrapMode="none">{barCharacter.repeat(200)}</text>
244
+ </box>
245
+ </box>
246
+ )
247
+ })}
248
+ {maxTotal > row.total && (
249
+ <box flexGrow={maxTotal - row.total} flexBasis={0} flexShrink={1} />
250
+ )}
251
+ </box>
252
+ </box>
253
+ )
254
+ })}
255
+ </box>
256
+
257
+ {legendVisible && (
258
+ <box flexDirection="column" width={legendWidth} flexShrink={0} overflow="hidden">
259
+ {legendRows.map((row) => {
260
+ return (
261
+ <box key={row.title} height={1} flexShrink={0} flexDirection="row" overflow="hidden">
262
+ <box width={legendGap} flexShrink={0} />
263
+ <text fg={row.color} wrapMode="none">● </text>
264
+ <text fg={theme.textMuted} wrapMode="none">
265
+ {padRight(row.title, legendTitleWidth)} {padLeft(row.percentage, legendPercentageWidth)}
266
+ </text>
267
+ </box>
268
+ )
269
+ })}
270
+ </box>
271
+ )}
272
+ </box>
273
+ </box>
274
+ )
275
+ }
276
+
277
+ HorizontalBarGraph.Series = HorizontalBarGraphSeries
278
+
279
+ export { HorizontalBarGraph }
@@ -374,6 +374,8 @@ export interface DropdownProps extends SearchBarInterface, CommonProps {
374
374
  storeValue?: boolean
375
375
  value?: string
376
376
  defaultValue?: string
377
+ /** Override the text shown in the search bar for the active selection. When set, this is displayed instead of the selected item's title. Useful for managed state where the display label doesn't match any dropdown item. */
378
+ displayValue?: string
377
379
  onChange?: (newValue: string) => void
378
380
  children?: ReactNode
379
381
  }
@@ -946,9 +948,9 @@ function ListItemRow(props: {
946
948
  <text flexShrink={0} fg={active ? theme.background : theme.text} attributes={active ? TextAttributes.BOLD : undefined} selectable={false} wrapMode="none">{active ? '›' : ' '}</text>
947
949
  {icon && <text flexShrink={0} fg={active ? theme.background : iconColor || theme.text} selectable={false} wrapMode="none">{getIconEmoji(icon)} </text>}
948
950
  <text
949
- flexShrink={0}
951
+ flexShrink={1}
952
+ truncate
950
953
  fg={active ? theme.background : theme.text}
951
-
952
954
  attributes={TextAttributes.BOLD}
953
955
  selectable={false}
954
956
  wrapMode="none"
@@ -969,9 +971,10 @@ function ListItemRow(props: {
969
971
  </box>
970
972
  {/* Line 2: subtitle indented to align with title */}
971
973
  {subtitle && (
972
- <box style={{ paddingLeft: subtitleIndent }}>
974
+ <box style={{ paddingLeft: subtitleIndent, overflow: 'hidden' }}>
973
975
  <text
974
- flexShrink={0}
976
+ flexShrink={1}
977
+ truncate
975
978
  fg={active ? theme.background : theme.text}
976
979
  selectable={false}
977
980
  wrapMode="none"
@@ -1006,11 +1009,12 @@ function ListItemRow(props: {
1006
1009
  onMouseDown={handleMouseDown}
1007
1010
  >
1008
1011
  <box style={{ flexDirection: 'row', flexGrow: 1, flexShrink: 1, overflow: 'hidden', gap: 1 }}>
1009
- <box style={{ flexDirection: 'row', flexShrink: 0 }}>
1012
+ <box style={{ flexDirection: 'row', flexShrink: 1, overflow: 'hidden' }}>
1010
1013
  <text flexShrink={0} fg={active ? theme.background : theme.text} attributes={active ? TextAttributes.BOLD : undefined} selectable={false} wrapMode="none">{active ? '›' : ' '}</text>
1011
1014
  {icon && <text flexShrink={0} fg={active ? theme.background : iconColor || theme.text} selectable={false} wrapMode="none">{getIconEmoji(icon)} </text>}
1012
1015
  <text
1013
- flexShrink={0}
1016
+ flexShrink={1}
1017
+ truncate
1014
1018
  fg={active ? theme.background : theme.text}
1015
1019
  attributes={active ? TextAttributes.BOLD : undefined}
1016
1020
  selectable={false}
@@ -1021,7 +1025,8 @@ function ListItemRow(props: {
1021
1025
  </box>
1022
1026
  {subtitle && (
1023
1027
  <text
1024
- flexShrink={0}
1028
+ flexShrink={3}
1029
+ truncate
1025
1030
  fg={active ? theme.background : theme.textMuted}
1026
1031
  selectable={false}
1027
1032
  wrapMode="none"
@@ -2127,9 +2132,9 @@ const ListDropdown: ListDropdownType = (props) => {
2127
2132
 
2128
2133
  const { isDropdownOpen, setIsDropdownOpen } = listContext
2129
2134
 
2130
- const setDropdownSelection = (props: { value: string; title: string }) => {
2131
- setDropdownState({ value: props.value, title: props.title })
2132
- useStore.setState({ dropdownFooterLabel: props.title || 'dropdown' })
2135
+ const setDropdownSelection = (selectionProps: { value: string; title: string }) => {
2136
+ setDropdownState({ value: selectionProps.value, title: selectionProps.title })
2137
+ useStore.setState({ dropdownFooterLabel: props.displayValue ?? (selectionProps.title || 'dropdown') })
2133
2138
  }
2134
2139
  // Store both value and title together
2135
2140
  const [dropdownState, setDropdownState] = useState<{
@@ -2168,7 +2173,7 @@ const ListDropdown: ListDropdownType = (props) => {
2168
2173
 
2169
2174
  if (!valueToUse) {
2170
2175
  useStore.setState({
2171
- dropdownFooterLabel: dropdownState.title || 'dropdown',
2176
+ dropdownFooterLabel: props.displayValue ?? (dropdownState.title || 'dropdown'),
2172
2177
  })
2173
2178
  return
2174
2179
  }
@@ -2189,8 +2194,8 @@ const ListDropdown: ListDropdownType = (props) => {
2189
2194
  return
2190
2195
  }
2191
2196
 
2192
- useStore.setState({ dropdownFooterLabel: title || 'dropdown' })
2193
- }, [props.value]) // Run when props.value changes and on mount
2197
+ useStore.setState({ dropdownFooterLabel: props.displayValue ?? (title || 'dropdown') })
2198
+ }, [props.value, props.displayValue]) // Run when props.value or displayValue changes and on mount
2194
2199
 
2195
2200
  const dropdownContextValue = useMemo<DropdownContextValue>(
2196
2201
  () => ({
@@ -2242,8 +2247,8 @@ const ListDropdown: ListDropdownType = (props) => {
2242
2247
  }
2243
2248
  }, [isDropdownOpen, props.children])
2244
2249
 
2245
- // Display the title from our state
2246
- const displayValue = dropdownState.title || 'All'
2250
+ // Display the title from our state, or use the caller's override
2251
+ const displayValue = props.displayValue ?? (dropdownState.title || 'All')
2247
2252
  const openDropdownIfClosed = () => {
2248
2253
  if (!isDropdownOpen) {
2249
2254
  listContext.openDropdown()
@@ -27,18 +27,18 @@ afterEach(() => {
27
27
  test('ctrl+r shortcut should trigger Refresh action directly', async () => {
28
28
  // Wait for list to render
29
29
  await session.text({
30
- waitFor: (text) => /Refresh count: 0/.test(text),
30
+ waitFor: (text) => /nt: 0/.test(text),
31
31
  })
32
32
 
33
33
  const initial = await session.text()
34
- expect(initial).toContain('Refresh count: 0')
34
+ expect(initial).toContain('nt: 0')
35
35
 
36
36
  // Press ctrl+r directly to trigger Refresh action
37
37
  await session.press(['ctrl', 'r'])
38
38
 
39
39
  // Wait for the refresh to take effect
40
40
  const afterCtrlR = await session.text({
41
- waitFor: (text) => /Refresh count: 1/.test(text),
41
+ waitFor: (text) => /nt: 1/.test(text),
42
42
  timeout: 5000,
43
43
  })
44
44
  expect(afterCtrlR).toMatchInlineSnapshot(`
@@ -49,7 +49,7 @@ test('ctrl+r shortcut should trigger Refresh action directly', async () => {
49
49
 
50
50
  > Search...
51
51
 
52
- Refresh count: 1 Press ctrl+r to refresh (shortcut) or Enter the
52
+ Refre...nt: 1Press ctrl+r to refresh ...nter then select Refresh
53
53
 
54
54
 
55
55
 
@@ -69,7 +69,7 @@ test('ctrl+r shortcut should trigger Refresh action directly', async () => {
69
69
  test('action shortcut is displayed in action panel', async () => {
70
70
  // Wait for list to render
71
71
  await session.text({
72
- waitFor: (text) => /Refresh count: 0/.test(text),
72
+ waitFor: (text) => /nt: 0/.test(text),
73
73
  })
74
74
 
75
75
  // Open action panel with ctrl+k
@@ -110,30 +110,30 @@ test('action shortcut is displayed in action panel', async () => {
110
110
  test('action works via Enter (auto-execute first action)', async () => {
111
111
  // Wait for list to render
112
112
  await session.text({
113
- waitFor: (text) => /Refresh count: 0/.test(text),
113
+ waitFor: (text) => /nt: 0/.test(text),
114
114
  })
115
115
 
116
116
  // Press Enter to auto-execute first action (Refresh)
117
117
  await session.press('return')
118
118
 
119
119
  const afterEnter = await session.text({
120
- waitFor: (text) => /Refresh count: 1/.test(text),
120
+ waitFor: (text) => /nt: 1/.test(text),
121
121
  timeout: 5000,
122
122
  })
123
123
 
124
- expect(afterEnter).toContain('Refresh count: 1')
124
+ expect(afterEnter).toContain('nt: 1')
125
125
  }, 30000)
126
126
 
127
127
  test('ctrl+x shortcut should trigger Reset action directly', async () => {
128
128
  // Wait for list to render and increment once via Enter
129
129
  await session.text({
130
- waitFor: (text) => /Refresh count: 0/.test(text),
130
+ waitFor: (text) => /nt: 0/.test(text),
131
131
  })
132
132
 
133
133
  // Increment via Enter first
134
134
  await session.press('return')
135
135
  await session.text({
136
- waitFor: (text) => /Refresh count: 1/.test(text),
136
+ waitFor: (text) => /nt: 1/.test(text),
137
137
  timeout: 5000,
138
138
  })
139
139
 
@@ -142,27 +142,27 @@ test('ctrl+x shortcut should trigger Reset action directly', async () => {
142
142
 
143
143
  // Wait for the reset to take effect
144
144
  const afterCtrlX = await session.text({
145
- waitFor: (text) => /Refresh count: 0/.test(text),
145
+ waitFor: (text) => /nt: 0/.test(text),
146
146
  timeout: 5000,
147
147
  })
148
- expect(afterCtrlX).toContain('Refresh count: 0')
148
+ expect(afterCtrlX).toContain('nt: 0')
149
149
  }, 30000)
150
150
 
151
151
  test('alt+d shortcut should trigger Double action directly', async () => {
152
152
  // Wait for list to render
153
153
  await session.text({
154
- waitFor: (text) => /Refresh count: 0/.test(text),
154
+ waitFor: (text) => /nt: 0/.test(text),
155
155
  })
156
156
 
157
157
  // Increment twice via Enter to get count=2
158
158
  await session.press('return')
159
159
  await session.text({
160
- waitFor: (text) => /Refresh count: 1/.test(text),
160
+ waitFor: (text) => /nt: 1/.test(text),
161
161
  timeout: 5000,
162
162
  })
163
163
  await session.press('return')
164
164
  await session.text({
165
- waitFor: (text) => /Refresh count: 2/.test(text),
165
+ waitFor: (text) => /nt: 2/.test(text),
166
166
  timeout: 5000,
167
167
  })
168
168
 
@@ -171,8 +171,8 @@ test('alt+d shortcut should trigger Double action directly', async () => {
171
171
 
172
172
  // Wait for the double to take effect
173
173
  const afterAltD = await session.text({
174
- waitFor: (text) => /Refresh count: 4/.test(text),
174
+ waitFor: (text) => /nt: 4/.test(text),
175
175
  timeout: 5000,
176
176
  })
177
- expect(afterAltD).toContain('Refresh count: 4')
177
+ expect(afterAltD).toContain('nt: 4')
178
178
  }, 30000)
@@ -151,7 +151,7 @@ function BarGraphWeeklyExample() {
151
151
  <List.Item.Detail
152
152
  metadata={
153
153
  <List.Item.Detail.Metadata>
154
- <BarGraph height={10} labels={manyColsLabels}>
154
+ <BarGraph height={10} labels={manyColsLabels} barWidth={1}>
155
155
  {manyColsSeries.map((s, i) => {
156
156
  return <BarGraph.Series key={i} data={s.data} title={s.title} />
157
157
  })}
@@ -261,4 +261,4 @@ function BarGraphWeeklyExample() {
261
261
  )
262
262
  }
263
263
 
264
- renderWithProviders(<BarGraphWeeklyExample />)
264
+ void renderWithProviders(<BarGraphWeeklyExample />)