termcast 1.3.47 → 1.3.49
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/apis/cache.d.ts.map +1 -1
- package/dist/apis/cache.js +1 -2
- package/dist/apis/cache.js.map +1 -1
- package/dist/apis/localstorage.d.ts.map +1 -1
- package/dist/apis/localstorage.js +1 -2
- package/dist/apis/localstorage.js.map +1 -1
- package/dist/apis/sqlite.d.ts +7 -0
- package/dist/apis/sqlite.d.ts.map +1 -0
- package/dist/apis/sqlite.js +13 -0
- package/dist/apis/sqlite.js.map +1 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +14 -5
- 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 +46 -27
- 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/logger.d.ts.map +1 -1
- package/dist/logger.js +2 -1
- package/dist/logger.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/swift-runtime.d.ts.map +1 -1
- package/dist/swift-runtime.js +20 -5
- package/dist/swift-runtime.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 +4 -1
- package/dist/utils.js.map +1 -1
- package/package.json +12 -4
- package/src/apis/cache.test.ts +1 -1
- package/src/apis/cache.tsx +1 -2
- package/src/apis/localstorage.tsx +1 -2
- package/src/apis/sqlite.ts +14 -0
- package/src/build.tsx +15 -5
- package/src/cli.tsx +5 -49
- package/src/colors.tsx +7 -7
- package/src/compile.tsx +53 -30
- 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 +22 -22
- 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 +73 -73
- 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 -18
- 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/logger.tsx +2 -1
- 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/swift-runtime.tsx +19 -5
- 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.test.tsx +1 -1
- package/src/utils.tsx +5 -1
package/src/components/list.tsx
CHANGED
|
@@ -132,23 +132,62 @@ function CurrentItemActionsOffscreen(props: {
|
|
|
132
132
|
const descendantsMap = useListDescendantsRerender()
|
|
133
133
|
|
|
134
134
|
// Get current item's actions
|
|
135
|
-
const
|
|
136
|
-
.
|
|
137
|
-
.sort((a, b) => a.index - b.index)
|
|
138
|
-
|
|
139
|
-
const currentItem = items.find((item) => item.index === props.selectedIndex)
|
|
135
|
+
const currentItem = Object.values(descendantsMap)
|
|
136
|
+
.find((item) => item.index === props.selectedIndex)
|
|
140
137
|
const actions = currentItem?.props?.actions ?? props.fallbackActions ?? null
|
|
141
138
|
|
|
142
|
-
|
|
139
|
+
const hasExtensionActions = !!actions
|
|
140
|
+
|
|
141
|
+
// Clear firstActionTitle when no extension actions exist, so the footer
|
|
142
|
+
// doesn't show "↵ change theme..." for built-in actions — Enter should only
|
|
143
|
+
// auto-execute extension-provided actions, not built-in ones like Change Theme.
|
|
144
|
+
// This runs after ActionPanel's own layoutEffect which would set it to the
|
|
145
|
+
// first built-in action title.
|
|
143
146
|
useLayoutEffect(() => {
|
|
144
|
-
if (!
|
|
147
|
+
if (!hasExtensionActions) {
|
|
145
148
|
useStore.setState({ firstActionTitle: '' })
|
|
146
149
|
}
|
|
147
|
-
}
|
|
150
|
+
})
|
|
148
151
|
|
|
149
|
-
|
|
152
|
+
// Always mount ActionPanel offscreen so built-in actions (Change Theme, etc.)
|
|
153
|
+
// are available via ctrl+k even when extension provides no actions
|
|
154
|
+
return <Offscreen>{actions || <ActionPanel />}</Offscreen>
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Reads the selected item's detail directly from committed descendants map.
|
|
159
|
+
* useDescendantsRerender now notifies on every commit (not just structural changes),
|
|
160
|
+
* so committedMap always has fresh props including detail.
|
|
161
|
+
*/
|
|
162
|
+
function CurrentItemDetail(props: {
|
|
163
|
+
selectedIndex: number
|
|
164
|
+
isShowingDetail?: boolean
|
|
165
|
+
}): any {
|
|
166
|
+
const theme = useTheme()
|
|
167
|
+
const descendantsMap = useListDescendantsRerender()
|
|
150
168
|
|
|
151
|
-
return
|
|
169
|
+
if (!props.isShowingDetail) return null
|
|
170
|
+
|
|
171
|
+
const currentItem = Object.values(descendantsMap)
|
|
172
|
+
.find((item) => item.index === props.selectedIndex)
|
|
173
|
+
const detail = currentItem?.props?.detail ?? null
|
|
174
|
+
|
|
175
|
+
if (!detail) return null
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
<box
|
|
179
|
+
style={{
|
|
180
|
+
width: '50%',
|
|
181
|
+
paddingLeft: 1,
|
|
182
|
+
paddingRight: 1,
|
|
183
|
+
}}
|
|
184
|
+
border={['left']}
|
|
185
|
+
borderStyle='single'
|
|
186
|
+
borderColor={theme.border}
|
|
187
|
+
>
|
|
188
|
+
{detail}
|
|
189
|
+
</box>
|
|
190
|
+
)
|
|
152
191
|
}
|
|
153
192
|
|
|
154
193
|
interface NavigationChildInterface {
|
|
@@ -208,9 +247,11 @@ export type ItemAccessory =
|
|
|
208
247
|
| {
|
|
209
248
|
tag?:
|
|
210
249
|
| string
|
|
250
|
+
| null
|
|
211
251
|
| {
|
|
212
252
|
value: string
|
|
213
253
|
color?: Color.ColorLike
|
|
254
|
+
key?: string
|
|
214
255
|
}
|
|
215
256
|
}
|
|
216
257
|
| {
|
|
@@ -302,11 +343,58 @@ export interface ListProps
|
|
|
302
343
|
selectedItemId?: string
|
|
303
344
|
isShowingDetail?: boolean
|
|
304
345
|
/**
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
346
|
+
* Controls the vertical spacing of list items.
|
|
347
|
+
* - 'default': Single-line items with title and subtitle on same row
|
|
348
|
+
* - 'relaxed': Two-line items with title on first row, subtitle below, and padding between items
|
|
349
|
+
*/
|
|
309
350
|
spacingMode?: ListSpacingMode
|
|
351
|
+
/**
|
|
352
|
+
* Fixed column widths (in terminal characters) for tag accessories,
|
|
353
|
+
* enabling table-like alignment across all list items.
|
|
354
|
+
*
|
|
355
|
+
* Each number in the array defines the display width for the Nth tag
|
|
356
|
+
* in each item's accessories array. Tags are left-aligned within their
|
|
357
|
+
* column using `padEnd`. Non-tag accessories (`text`, `date`) are not
|
|
358
|
+
* affected and render with their natural width.
|
|
359
|
+
*
|
|
360
|
+
* **Requirements:**
|
|
361
|
+
* - All items should have the same number of tag accessories in the
|
|
362
|
+
* same order. Use `{ tag: null }` as a placeholder for missing tags
|
|
363
|
+
* to preserve column alignment.
|
|
364
|
+
* - Each width should be at least the length of the longest tag value
|
|
365
|
+
* at that position. Tags render as plain text (no brackets added).
|
|
366
|
+
* - The array length should match the number of tag positions.
|
|
367
|
+
*
|
|
368
|
+
* **Example:**
|
|
369
|
+
* ```tsx
|
|
370
|
+
* // Widths: comments max "12 comments"=11, status max "In Progress"=11, priority max "P3"=2
|
|
371
|
+
* <List accessoryTagsLayout={[11, 11, 2]}>
|
|
372
|
+
* <List.Item
|
|
373
|
+
* title="Fix login bug"
|
|
374
|
+
* accessories={[
|
|
375
|
+
* { tag: { value: '3 comments', key: 'comments' } },
|
|
376
|
+
* { tag: { value: 'Open', color: Color.Green, key: 'status' } },
|
|
377
|
+
* { tag: { value: 'P1', color: Color.Red, key: 'priority' } },
|
|
378
|
+
* { date: new Date() },
|
|
379
|
+
* ]}
|
|
380
|
+
* />
|
|
381
|
+
* <List.Item
|
|
382
|
+
* title="Refactor auth"
|
|
383
|
+
* accessories={[
|
|
384
|
+
* { tag: { value: '7 comments', key: 'comments' } },
|
|
385
|
+
* { tag: { value: 'Closed', color: Color.Purple, key: 'status' } },
|
|
386
|
+
* { tag: null }, // placeholder for missing priority column
|
|
387
|
+
* { date: new Date() },
|
|
388
|
+
* ]}
|
|
389
|
+
* />
|
|
390
|
+
* </List>
|
|
391
|
+
*
|
|
392
|
+
* // Renders as:
|
|
393
|
+
* // Fix login bug 3 comments Open P1 1d
|
|
394
|
+
* // Refactor auth 7 comments Closed 2w
|
|
395
|
+
* ```
|
|
396
|
+
*/
|
|
397
|
+
accessoryTagsLayout?: number[]
|
|
310
398
|
}
|
|
311
399
|
|
|
312
400
|
interface ListType {
|
|
@@ -370,12 +458,12 @@ interface ListContextValue {
|
|
|
370
458
|
setSelectedIndex?: (index: number) => void
|
|
371
459
|
searchText: string
|
|
372
460
|
isFiltering: boolean
|
|
373
|
-
setCurrentDetail?: (detail: ReactNode) => void
|
|
374
461
|
isShowingDetail?: boolean
|
|
375
462
|
customEmptyViewRef: React.MutableRefObject<boolean>
|
|
376
463
|
isLoading?: boolean
|
|
377
464
|
hasDropdown?: boolean
|
|
378
465
|
spacingMode: ListSpacingMode
|
|
466
|
+
accessoryTagWidths?: number[]
|
|
379
467
|
}
|
|
380
468
|
|
|
381
469
|
const ListContext = createContext<ListContextValue | undefined>(undefined)
|
|
@@ -405,6 +493,16 @@ function shouldItemBeVisible(
|
|
|
405
493
|
return searchableText.includes(needle)
|
|
406
494
|
}
|
|
407
495
|
|
|
496
|
+
// Get the foreground color for a tag accessory (used in both auto and table modes)
|
|
497
|
+
function getTagColor(tag: ItemAccessory, theme: ReturnType<typeof useTheme>, active: boolean): string | undefined {
|
|
498
|
+
if (active) return theme.background
|
|
499
|
+
if ('tag' in tag && tag.tag) {
|
|
500
|
+
const tagColor = typeof tag.tag === 'object' ? tag.tag?.color : undefined
|
|
501
|
+
return resolveColor(tagColor) || theme.warning
|
|
502
|
+
}
|
|
503
|
+
return undefined
|
|
504
|
+
}
|
|
505
|
+
|
|
408
506
|
// Create descendants for List items
|
|
409
507
|
interface ListItemDescendant {
|
|
410
508
|
id?: string
|
|
@@ -651,12 +749,14 @@ function ListItemRow(props: {
|
|
|
651
749
|
const theme = useTheme()
|
|
652
750
|
const listCtx = useContext(ListContext)
|
|
653
751
|
const spacingMode = listCtx?.spacingMode ?? 'default'
|
|
752
|
+
const accessoryTagWidths = listCtx?.accessoryTagWidths
|
|
654
753
|
const isRelaxed = spacingMode === 'relaxed'
|
|
655
754
|
const { title, subtitle, icon, iconColor, accessories, active, ref } = props
|
|
656
755
|
const [isHovered, setIsHovered] = useState(false)
|
|
657
756
|
|
|
658
757
|
const accessoryElements: ReactNode[] = []
|
|
659
758
|
if (accessories) {
|
|
759
|
+
let tagIndex = 0
|
|
660
760
|
accessories.forEach((accessory) => {
|
|
661
761
|
if ('text' in accessory && accessory.text) {
|
|
662
762
|
const textValue =
|
|
@@ -678,25 +778,35 @@ function ListItemRow(props: {
|
|
|
678
778
|
)
|
|
679
779
|
}
|
|
680
780
|
}
|
|
681
|
-
if ('tag' in accessory
|
|
781
|
+
if ('tag' in accessory) {
|
|
782
|
+
const colWidth = accessoryTagWidths?.[tagIndex]
|
|
682
783
|
const tagValue =
|
|
683
784
|
typeof accessory.tag === 'string'
|
|
684
785
|
? accessory.tag
|
|
685
786
|
: accessory.tag?.value
|
|
686
|
-
const tagColor =
|
|
687
|
-
typeof accessory.tag === 'object' ? accessory.tag?.color : undefined
|
|
688
787
|
if (tagValue) {
|
|
788
|
+
const tagColor = getTagColor(accessory, theme, !!active)
|
|
789
|
+
const displayText = tagValue
|
|
790
|
+
const padded = colWidth ? displayText.padEnd(colWidth) : displayText
|
|
689
791
|
accessoryElements.push(
|
|
690
792
|
<text
|
|
691
|
-
key={`tag-${
|
|
793
|
+
key={`tag-${tagIndex}`}
|
|
692
794
|
flexShrink={0}
|
|
693
|
-
fg={
|
|
795
|
+
fg={tagColor}
|
|
694
796
|
wrapMode="none"
|
|
695
797
|
>
|
|
696
|
-
|
|
798
|
+
{padded}
|
|
799
|
+
</text>,
|
|
800
|
+
)
|
|
801
|
+
} else if (colWidth) {
|
|
802
|
+
// Null/empty tag → empty space placeholder to preserve column alignment
|
|
803
|
+
accessoryElements.push(
|
|
804
|
+
<text key={`tag-empty-${tagIndex}`} flexShrink={0} wrapMode="none">
|
|
805
|
+
{' '.repeat(colWidth)}
|
|
697
806
|
</text>,
|
|
698
807
|
)
|
|
699
808
|
}
|
|
809
|
+
tagIndex++
|
|
700
810
|
}
|
|
701
811
|
if ('date' in accessory && accessory.date) {
|
|
702
812
|
const dateValue =
|
|
@@ -722,6 +832,7 @@ function ListItemRow(props: {
|
|
|
722
832
|
}
|
|
723
833
|
}
|
|
724
834
|
})
|
|
835
|
+
|
|
725
836
|
}
|
|
726
837
|
|
|
727
838
|
// Calculate subtitle indentation to align with title start
|
|
@@ -877,6 +988,8 @@ export const List: ListType = (props) => {
|
|
|
877
988
|
selectedItemId,
|
|
878
989
|
searchBarAccessory,
|
|
879
990
|
spacingMode = 'default',
|
|
991
|
+
accessoryTagsLayout,
|
|
992
|
+
throttle,
|
|
880
993
|
...otherProps
|
|
881
994
|
} = props
|
|
882
995
|
|
|
@@ -886,15 +999,29 @@ export const List: ListType = (props) => {
|
|
|
886
999
|
const currentItem = stack[stack.length - 1]
|
|
887
1000
|
return currentItem?.selectedListIndex
|
|
888
1001
|
})
|
|
889
|
-
const
|
|
1002
|
+
const currentStackSearchText = useStore((state) => {
|
|
1003
|
+
const stack = state.navigationStack
|
|
1004
|
+
const currentItem = stack[stack.length - 1]
|
|
1005
|
+
return currentItem?.searchText
|
|
1006
|
+
})
|
|
1007
|
+
const [internalSearchText, setInternalSearchText] = useState(
|
|
1008
|
+
() => currentStackSearchText ?? '',
|
|
1009
|
+
)
|
|
890
1010
|
const [selectedIndex, setSelectedIndex] = useState<number>(() => {
|
|
891
1011
|
return currentStackSelectedListIndex ?? 0
|
|
892
1012
|
})
|
|
893
1013
|
const [isDropdownOpen, setIsDropdownOpen] = useState(false)
|
|
894
|
-
const [currentDetail, setCurrentDetail] = useState<ReactNode>(null)
|
|
895
1014
|
|
|
896
1015
|
const inputRef = useRef<TextareaRenderable>(null)
|
|
897
1016
|
const customEmptyViewRef = useRef(false)
|
|
1017
|
+
const throttleTimeoutRef = useRef<NodeJS.Timeout | undefined>(undefined)
|
|
1018
|
+
useEffect(() => {
|
|
1019
|
+
return () => {
|
|
1020
|
+
if (throttleTimeoutRef.current) {
|
|
1021
|
+
clearTimeout(throttleTimeoutRef.current)
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
}, [])
|
|
898
1025
|
|
|
899
1026
|
// Ref callback that registers the textarea in global state for ESC handling
|
|
900
1027
|
const setInputRef = useCallback((node: TextareaRenderable | null) => {
|
|
@@ -984,6 +1111,28 @@ export const List: ListType = (props) => {
|
|
|
984
1111
|
})
|
|
985
1112
|
}
|
|
986
1113
|
|
|
1114
|
+
// Persist search text in the navigation stack item so it survives push/pop.
|
|
1115
|
+
// Same pattern as persistSelectedIndexInCurrentNavigationItem.
|
|
1116
|
+
const persistSearchTextInCurrentNavigationItem = (text: string) => {
|
|
1117
|
+
useStore.setState((state) => {
|
|
1118
|
+
const stack = state.navigationStack
|
|
1119
|
+
const currentIndex = stack.length - 1
|
|
1120
|
+
const currentItem = stack[currentIndex]
|
|
1121
|
+
if (!currentItem) {
|
|
1122
|
+
return {}
|
|
1123
|
+
}
|
|
1124
|
+
if (currentItem.searchText === text) {
|
|
1125
|
+
return {}
|
|
1126
|
+
}
|
|
1127
|
+
const nextStack = [...stack]
|
|
1128
|
+
nextStack[currentIndex] = {
|
|
1129
|
+
...currentItem,
|
|
1130
|
+
searchText: text,
|
|
1131
|
+
}
|
|
1132
|
+
return { navigationStack: nextStack }
|
|
1133
|
+
})
|
|
1134
|
+
}
|
|
1135
|
+
|
|
987
1136
|
const setSelectedIndexWithPersistence = (index: number) => {
|
|
988
1137
|
setSelectedIndex(index)
|
|
989
1138
|
persistSelectedIndexInCurrentNavigationItem(index)
|
|
@@ -1037,23 +1186,16 @@ export const List: ListType = (props) => {
|
|
|
1037
1186
|
setSelectedIndex: setSelectedIndexWithPersistence,
|
|
1038
1187
|
searchText,
|
|
1039
1188
|
isFiltering: isFilteringEnabled,
|
|
1040
|
-
setCurrentDetail,
|
|
1041
1189
|
isShowingDetail,
|
|
1042
1190
|
customEmptyViewRef,
|
|
1043
1191
|
isLoading,
|
|
1044
1192
|
hasDropdown: !!searchBarAccessory,
|
|
1045
1193
|
spacingMode,
|
|
1194
|
+
accessoryTagWidths: accessoryTagsLayout,
|
|
1046
1195
|
}),
|
|
1047
|
-
[isDropdownOpen, selectedIndex, searchText, isFilteringEnabled, isShowingDetail, isLoading, searchBarAccessory, spacingMode],
|
|
1196
|
+
[isDropdownOpen, selectedIndex, searchText, isFilteringEnabled, isShowingDetail, isLoading, searchBarAccessory, spacingMode, accessoryTagsLayout],
|
|
1048
1197
|
)
|
|
1049
1198
|
|
|
1050
|
-
// Clear detail when detail view is hidden (before paint to avoid flash)
|
|
1051
|
-
useLayoutEffect(() => {
|
|
1052
|
-
if (!isShowingDetail) {
|
|
1053
|
-
setCurrentDetail(null)
|
|
1054
|
-
}
|
|
1055
|
-
}, [isShowingDetail])
|
|
1056
|
-
|
|
1057
1199
|
// Handle selectedItemId prop changes (before paint to avoid flash)
|
|
1058
1200
|
useLayoutEffect(() => {
|
|
1059
1201
|
// Only update selection if selectedItemId is explicitly provided
|
|
@@ -1220,11 +1362,9 @@ export const List: ListType = (props) => {
|
|
|
1220
1362
|
const currentItem = items.find((item) => item.index === selectedIndex)
|
|
1221
1363
|
|
|
1222
1364
|
// Handle Ctrl+K to show actions dialog via portal
|
|
1365
|
+
// Always open — built-in actions (Change Theme, etc.) are always available
|
|
1223
1366
|
if (evt.name === 'k' && evt.ctrl) {
|
|
1224
|
-
|
|
1225
|
-
if (hasActions) {
|
|
1226
|
-
useStore.setState({ showActionsDialog: true })
|
|
1227
|
-
}
|
|
1367
|
+
useStore.setState({ showActionsDialog: true })
|
|
1228
1368
|
return
|
|
1229
1369
|
}
|
|
1230
1370
|
|
|
@@ -1243,14 +1383,25 @@ export const List: ListType = (props) => {
|
|
|
1243
1383
|
const handleSearchChange = (newValue: string) => {
|
|
1244
1384
|
if (!inFocus) return
|
|
1245
1385
|
|
|
1246
|
-
// Always
|
|
1247
|
-
|
|
1248
|
-
onSearchTextChange(newValue)
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1386
|
+
// Always update internal state immediately so the textarea and filtering
|
|
1387
|
+
// stay responsive even when throttle delays the parent callback
|
|
1251
1388
|
if (controlledSearchText === undefined) {
|
|
1252
1389
|
setInternalSearchText(newValue)
|
|
1253
1390
|
}
|
|
1391
|
+
persistSearchTextInCurrentNavigationItem(newValue)
|
|
1392
|
+
|
|
1393
|
+
if (onSearchTextChange) {
|
|
1394
|
+
if (throttle) {
|
|
1395
|
+
if (throttleTimeoutRef.current) {
|
|
1396
|
+
clearTimeout(throttleTimeoutRef.current)
|
|
1397
|
+
}
|
|
1398
|
+
throttleTimeoutRef.current = setTimeout(() => {
|
|
1399
|
+
onSearchTextChange(newValue)
|
|
1400
|
+
}, 300)
|
|
1401
|
+
} else {
|
|
1402
|
+
onSearchTextChange(newValue)
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1254
1405
|
}
|
|
1255
1406
|
|
|
1256
1407
|
return (
|
|
@@ -1363,21 +1514,10 @@ export const List: ListType = (props) => {
|
|
|
1363
1514
|
</box>
|
|
1364
1515
|
|
|
1365
1516
|
{/* Detail panel on the right */}
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
width: '50%',
|
|
1371
|
-
paddingLeft: 1,
|
|
1372
|
-
paddingRight: 1,
|
|
1373
|
-
}}
|
|
1374
|
-
border={['left']}
|
|
1375
|
-
borderStyle='single'
|
|
1376
|
-
borderColor={theme.border}
|
|
1377
|
-
>
|
|
1378
|
-
{currentDetail}
|
|
1379
|
-
</box>
|
|
1380
|
-
)}
|
|
1517
|
+
<CurrentItemDetail
|
|
1518
|
+
selectedIndex={selectedIndex}
|
|
1519
|
+
isShowingDetail={isShowingDetail}
|
|
1520
|
+
/>
|
|
1381
1521
|
</box>
|
|
1382
1522
|
</box>
|
|
1383
1523
|
</ListDescendantsProvider>
|
|
@@ -1388,28 +1528,14 @@ export const List: ListType = (props) => {
|
|
|
1388
1528
|
|
|
1389
1529
|
// Wrapper component that only renders children when no visible items exist
|
|
1390
1530
|
function ShowOnNoItems(props: { children: ReactNode; isCustomEmptyView?: boolean }): any {
|
|
1391
|
-
|
|
1392
|
-
void useListDescendantsRerender()
|
|
1393
|
-
// Get live map ref for reading in useLayoutEffect
|
|
1394
|
-
const map = useListDescendantsMap()
|
|
1531
|
+
const descendantsMap = useListDescendantsRerender()
|
|
1395
1532
|
const listContext = useContext(ListContext)
|
|
1396
|
-
const [hasVisibleItems, setHasVisibleItems] = useState(true)
|
|
1397
|
-
|
|
1398
|
-
// We must check visibility in useLayoutEffect because:
|
|
1399
|
-
// 1. map.current is cleared by reset() during render, so it's empty if read during render
|
|
1400
|
-
// 2. committedMap is stale - it's a snapshot from the previous render cycle and doesn't
|
|
1401
|
-
// reflect prop changes like 'visible' (only tracks which items exist, not their props)
|
|
1402
|
-
// 3. Items register in their own useLayoutEffect, so map.current is only populated after
|
|
1403
|
-
// all items' layout effects have run
|
|
1404
|
-
useLayoutEffect(() => {
|
|
1405
|
-
const items = Object.values(map.current)
|
|
1406
|
-
.filter((item) => item.index !== -1 && item.props?.visible !== false)
|
|
1407
|
-
// For default empty view, also check if custom empty view exists
|
|
1408
|
-
const hasCustomEmptyView = !props.isCustomEmptyView && (listContext?.customEmptyViewRef.current ?? false)
|
|
1409
|
-
setHasVisibleItems(items.length > 0 || hasCustomEmptyView)
|
|
1410
|
-
})
|
|
1411
1533
|
|
|
1412
|
-
|
|
1534
|
+
const hasVisibleItems = Object.values(descendantsMap)
|
|
1535
|
+
.some((item) => item.index !== -1 && item.props?.visible !== false)
|
|
1536
|
+
const hasCustomEmptyView = !props.isCustomEmptyView && (listContext?.customEmptyViewRef.current ?? false)
|
|
1537
|
+
|
|
1538
|
+
if (hasVisibleItems || hasCustomEmptyView) return null
|
|
1413
1539
|
|
|
1414
1540
|
return props.children
|
|
1415
1541
|
}
|
|
@@ -1520,13 +1646,6 @@ const ListItem: ListItemType = (props) => {
|
|
|
1520
1646
|
const selectedIndex = listContext?.selectedIndex ?? 0
|
|
1521
1647
|
const isActive = index === selectedIndex
|
|
1522
1648
|
|
|
1523
|
-
// Update detail when this item becomes active or detail prop changes (before paint)
|
|
1524
|
-
useLayoutEffect(() => {
|
|
1525
|
-
if (isActive && listContext?.isShowingDetail && listContext?.setCurrentDetail) {
|
|
1526
|
-
listContext.setCurrentDetail(props.detail || null)
|
|
1527
|
-
}
|
|
1528
|
-
}, [isActive, props.detail, listContext?.isShowingDetail, listContext?.setCurrentDetail])
|
|
1529
|
-
|
|
1530
1649
|
// Don't render if not visible
|
|
1531
1650
|
if (!isVisible) return null
|
|
1532
1651
|
|
|
@@ -1627,19 +1746,10 @@ const ListItemDetail: ListItemDetailType = (props) => {
|
|
|
1627
1746
|
}}
|
|
1628
1747
|
>
|
|
1629
1748
|
<box gap={1} style={{ flexDirection: 'column' }}>
|
|
1630
|
-
{markdown && (
|
|
1749
|
+
{markdown && markdown.trim().length > 0 && (
|
|
1631
1750
|
<ListMarkdownContent markdown={markdown} />
|
|
1632
1751
|
)}
|
|
1633
|
-
{metadata
|
|
1634
|
-
<box
|
|
1635
|
-
style={{ paddingTop: 1 }}
|
|
1636
|
-
// border={['top']}
|
|
1637
|
-
// borderStyle='single'
|
|
1638
|
-
// borderColor={theme.border}
|
|
1639
|
-
>
|
|
1640
|
-
{metadata}
|
|
1641
|
-
</box>
|
|
1642
|
-
)}
|
|
1752
|
+
{metadata}
|
|
1643
1753
|
</box>
|
|
1644
1754
|
</ScrollBox>
|
|
1645
1755
|
</box>
|
|
@@ -1665,13 +1775,13 @@ const ListItemDetailMetadata = (props: MetadataProps) => {
|
|
|
1665
1775
|
const listDetailMetadataConfig: MetadataConfig = {
|
|
1666
1776
|
maxValueLen: 20,
|
|
1667
1777
|
titleMinWidth: computedTitleWidth,
|
|
1668
|
-
paddingBottom:
|
|
1778
|
+
paddingBottom: 0,
|
|
1669
1779
|
separatorWidth: 200, // Will be clipped by overflow: hidden
|
|
1670
1780
|
}
|
|
1671
1781
|
|
|
1672
1782
|
return (
|
|
1673
1783
|
<MetadataContext.Provider value={listDetailMetadataConfig}>
|
|
1674
|
-
<box style={{ flexDirection: 'column' }}>
|
|
1784
|
+
<box gap={1} style={{ flexDirection: 'column' }}>
|
|
1675
1785
|
{props.children}
|
|
1676
1786
|
</box>
|
|
1677
1787
|
</MetadataContext.Provider>
|
|
@@ -1812,7 +1922,6 @@ const ListDropdown: ListDropdownType = (props) => {
|
|
|
1812
1922
|
<box
|
|
1813
1923
|
key={dropdownState.value}
|
|
1814
1924
|
style={{
|
|
1815
|
-
paddingTop: 1,
|
|
1816
1925
|
paddingLeft: 2,
|
|
1817
1926
|
// minWidth: value.length + 4,
|
|
1818
1927
|
flexDirection: 'row',
|
|
@@ -2069,10 +2178,9 @@ function EmptyViewContent(props: EmptyViewProps): any {
|
|
|
2069
2178
|
if (!inFocus) return
|
|
2070
2179
|
|
|
2071
2180
|
// Handle Ctrl+K to show actions dialog via portal
|
|
2181
|
+
// Always open — built-in actions (Change Theme, etc.) are always available
|
|
2072
2182
|
if (evt.name === 'k' && evt.ctrl) {
|
|
2073
|
-
|
|
2074
|
-
useStore.setState({ showActionsDialog: true })
|
|
2075
|
-
}
|
|
2183
|
+
useStore.setState({ showActionsDialog: true })
|
|
2076
2184
|
return
|
|
2077
2185
|
}
|
|
2078
2186
|
|
|
@@ -2113,8 +2221,8 @@ function EmptyViewContent(props: EmptyViewProps): any {
|
|
|
2113
2221
|
{props.description?.replace(/\bRaycast\b/g, 'Termcast').replace(/\braycast\b/g, 'termcast') || ''}
|
|
2114
2222
|
</text>
|
|
2115
2223
|
)}
|
|
2116
|
-
{/*
|
|
2117
|
-
{props.actions
|
|
2224
|
+
{/* Always mount ActionPanel offscreen so built-in actions are available */}
|
|
2225
|
+
<Offscreen>{props.actions || <ActionPanel />}</Offscreen>
|
|
2118
2226
|
</box>
|
|
2119
2227
|
)
|
|
2120
2228
|
}
|
|
@@ -79,7 +79,7 @@ export function LoadingBar(props: LoadingBarProps): any {
|
|
|
79
79
|
const getCharacterColor = (index: number): string => {
|
|
80
80
|
if (!isLoading) {
|
|
81
81
|
// When not loading, use default theme colors
|
|
82
|
-
return index < title.length ? theme.text :
|
|
82
|
+
return index < title.length ? theme.text : theme.border
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
// Title text stays static when loading, only animate the bar
|
|
@@ -96,8 +96,8 @@ export function LoadingBar(props: LoadingBarProps): any {
|
|
|
96
96
|
return waveColors[distance]
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
// Default muted color for characters outside the wave
|
|
100
|
-
return
|
|
99
|
+
// Default muted color for characters outside the wave
|
|
100
|
+
return theme.border
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
return (
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { colord } from 'colord'
|
|
3
3
|
import { useAnimationTick, TICK_DIVISORS } from 'termcast/src/components/animation-tick'
|
|
4
|
+
import { useTheme } from 'termcast/src/theme'
|
|
4
5
|
|
|
5
6
|
interface LoadingTextProps {
|
|
6
7
|
children: string
|
|
7
8
|
isLoading?: boolean
|
|
8
|
-
color
|
|
9
|
+
color?: string
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
/**
|
|
@@ -21,7 +22,8 @@ function generateWaveColors(baseColor: string): string[] {
|
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
export function LoadingText(props: LoadingTextProps): any {
|
|
24
|
-
const
|
|
25
|
+
const theme = useTheme()
|
|
26
|
+
const { children, isLoading = false, color = theme.primary } = props
|
|
25
27
|
const tick = useAnimationTick(isLoading ? TICK_DIVISORS.LOADING_TEXT : 0)
|
|
26
28
|
|
|
27
29
|
const characters = children.split('')
|
|
@@ -35,7 +35,7 @@ interface MetadataConfig {
|
|
|
35
35
|
const defaultConfig: MetadataConfig = {
|
|
36
36
|
maxValueLen: 20,
|
|
37
37
|
titleMinWidth: 12,
|
|
38
|
-
paddingBottom:
|
|
38
|
+
paddingBottom: 0,
|
|
39
39
|
separatorWidth: 30,
|
|
40
40
|
}
|
|
41
41
|
|
|
@@ -255,7 +255,7 @@ const Metadata: MetadataType = (props) => {
|
|
|
255
255
|
|
|
256
256
|
return (
|
|
257
257
|
<MetadataContext.Provider value={config}>
|
|
258
|
-
<box style={{ flexDirection: 'column' }}>{props.children}</box>
|
|
258
|
+
<box gap={1} style={{ flexDirection: 'column' }}>{props.children}</box>
|
|
259
259
|
</MetadataContext.Provider>
|
|
260
260
|
)
|
|
261
261
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Row component - horizontal layout container that distributes space evenly.
|
|
2
|
+
// Wraps each child in a flex-grow box so they split available width equally.
|
|
3
|
+
// Useful for placing multiple graphs or detail panels side by side.
|
|
4
|
+
|
|
5
|
+
import { BoxProps } from '@opentui/react'
|
|
6
|
+
import React, { ReactNode } from 'react'
|
|
7
|
+
|
|
8
|
+
export interface RowProps extends BoxProps {
|
|
9
|
+
/** Gap between children in columns (default: 1) */
|
|
10
|
+
gap?: number
|
|
11
|
+
|
|
12
|
+
children: ReactNode
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function Row(props: RowProps): any {
|
|
16
|
+
const { gap = 1, children, ...rest } = props
|
|
17
|
+
return (
|
|
18
|
+
<box flexDirection="row" gap={gap} width="100%" {...rest}>
|
|
19
|
+
{React.Children.map(children, (child) => {
|
|
20
|
+
if (!React.isValidElement(child)) return child
|
|
21
|
+
return (
|
|
22
|
+
<box flexGrow={1} flexBasis={0} flexShrink={1}>
|
|
23
|
+
{child}
|
|
24
|
+
</box>
|
|
25
|
+
)
|
|
26
|
+
})}
|
|
27
|
+
</box>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export { Row }
|