termcast 1.3.32 → 1.3.34
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/action-utils.d.ts.map +1 -1
- package/dist/action-utils.js +8 -0
- package/dist/action-utils.js.map +1 -1
- package/dist/apis/cache.d.ts +1 -2
- package/dist/apis/cache.d.ts.map +1 -1
- package/dist/apis/cache.js +138 -54
- package/dist/apis/cache.js.map +1 -1
- package/dist/apis/clipboard.d.ts.map +1 -1
- package/dist/apis/clipboard.js +4 -0
- package/dist/apis/clipboard.js.map +1 -1
- package/dist/apis/oauth.d.ts.map +1 -1
- package/dist/apis/oauth.js +31 -4
- package/dist/apis/oauth.js.map +1 -1
- package/dist/build.d.ts +0 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +30 -51
- package/dist/build.js.map +1 -1
- package/dist/cli.js +31 -14
- package/dist/cli.js.map +1 -1
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +5 -1
- package/dist/compile.js.map +1 -1
- package/dist/components/actions.d.ts +14 -0
- package/dist/components/actions.d.ts.map +1 -1
- package/dist/components/actions.js +151 -59
- package/dist/components/actions.js.map +1 -1
- package/dist/components/alert.d.ts.map +1 -1
- package/dist/components/alert.js +6 -5
- package/dist/components/alert.js.map +1 -1
- package/dist/components/animation-tick.d.ts +1 -1
- package/dist/components/animation-tick.js +1 -1
- package/dist/components/animation-tick.js.map +1 -1
- package/dist/components/detail.d.ts +5 -31
- package/dist/components/detail.d.ts.map +1 -1
- package/dist/components/detail.js +36 -52
- package/dist/components/detail.js.map +1 -1
- package/dist/components/dropdown.d.ts +1 -1
- package/dist/components/dropdown.d.ts.map +1 -1
- package/dist/components/dropdown.js +50 -22
- package/dist/components/dropdown.js.map +1 -1
- package/dist/components/footer.d.ts.map +1 -1
- package/dist/components/footer.js +19 -18
- package/dist/components/footer.js.map +1 -1
- package/dist/components/form/checkbox.d.ts.map +1 -1
- package/dist/components/form/checkbox.js +12 -11
- package/dist/components/form/checkbox.js.map +1 -1
- package/dist/components/form/date-picker.d.ts.map +1 -1
- package/dist/components/form/date-picker.js +7 -22
- package/dist/components/form/date-picker.js.map +1 -1
- package/dist/components/form/description.d.ts +1 -1
- package/dist/components/form/description.d.ts.map +1 -1
- package/dist/components/form/description.js +6 -5
- package/dist/components/form/description.js.map +1 -1
- package/dist/components/form/dropdown.d.ts.map +1 -1
- package/dist/components/form/dropdown.js +53 -50
- package/dist/components/form/dropdown.js.map +1 -1
- package/dist/components/form/file-autocomplete.d.ts.map +1 -1
- package/dist/components/form/file-autocomplete.js +5 -4
- package/dist/components/form/file-autocomplete.js.map +1 -1
- package/dist/components/form/file-picker.d.ts.map +1 -1
- package/dist/components/form/file-picker.js +23 -22
- package/dist/components/form/file-picker.js.map +1 -1
- package/dist/components/form/form-end.d.ts.map +1 -1
- package/dist/components/form/form-end.js +6 -4
- package/dist/components/form/form-end.js.map +1 -1
- package/dist/components/form/form-field-wrapper.d.ts +15 -0
- package/dist/components/form/form-field-wrapper.d.ts.map +1 -0
- package/dist/components/form/form-field-wrapper.js +29 -0
- package/dist/components/form/form-field-wrapper.js.map +1 -0
- package/dist/components/form/index.d.ts.map +1 -1
- package/dist/components/form/index.js +31 -30
- package/dist/components/form/index.js.map +1 -1
- package/dist/components/form/password-field.d.ts.map +1 -1
- package/dist/components/form/password-field.js +7 -6
- package/dist/components/form/password-field.js.map +1 -1
- package/dist/components/form/separator.d.ts.map +1 -1
- package/dist/components/form/separator.js +3 -2
- package/dist/components/form/separator.js.map +1 -1
- package/dist/components/form/tagpicker.d.ts.map +1 -1
- package/dist/components/form/tagpicker.js +2 -1
- package/dist/components/form/tagpicker.js.map +1 -1
- package/dist/components/form/text-area.d.ts.map +1 -1
- package/dist/components/form/text-area.js +7 -6
- package/dist/components/form/text-area.js.map +1 -1
- package/dist/components/form/text-field.d.ts.map +1 -1
- package/dist/components/form/text-field.js +7 -6
- package/dist/components/form/text-field.js.map +1 -1
- package/dist/components/form/use-form-navigation.d.ts.map +1 -1
- package/dist/components/form/use-form-navigation.js +4 -4
- package/dist/components/form/use-form-navigation.js.map +1 -1
- package/dist/components/form/with-left-border.d.ts +15 -0
- package/dist/components/form/with-left-border.d.ts.map +1 -1
- package/dist/components/form/with-left-border.js +21 -9
- package/dist/components/form/with-left-border.js.map +1 -1
- package/dist/components/icon.d.ts +14 -0
- package/dist/components/icon.d.ts.map +1 -1
- package/dist/components/icon.js +60 -0
- package/dist/components/icon.js.map +1 -1
- package/dist/components/image.d.ts +47 -2
- package/dist/components/image.d.ts.map +1 -1
- package/dist/components/image.js +46 -7
- package/dist/components/image.js.map +1 -1
- package/dist/components/list.d.ts +5 -0
- package/dist/components/list.d.ts.map +1 -1
- package/dist/components/list.js +188 -132
- package/dist/components/list.js.map +1 -1
- package/dist/components/loading-bar.d.ts.map +1 -1
- package/dist/components/loading-bar.js +4 -3
- package/dist/components/loading-bar.js.map +1 -1
- package/dist/components/metadata.d.ts +70 -0
- package/dist/components/metadata.d.ts.map +1 -0
- package/dist/components/metadata.js +82 -0
- package/dist/components/metadata.js.map +1 -0
- package/dist/components/theme-picker.d.ts.map +1 -1
- package/dist/components/theme-picker.js +3 -2
- package/dist/components/theme-picker.js.map +1 -1
- package/dist/descendants-v2.d.ts +60 -0
- package/dist/descendants-v2.d.ts.map +1 -0
- package/dist/descendants-v2.js +144 -0
- package/dist/descendants-v2.js.map +1 -0
- package/dist/examples/actions-context.d.ts +2 -0
- package/dist/examples/actions-context.d.ts.map +1 -0
- package/dist/examples/actions-context.js +33 -0
- package/dist/examples/actions-context.js.map +1 -0
- package/dist/examples/form-basic.d.ts.map +1 -1
- package/dist/examples/form-basic.js +1 -1
- package/dist/examples/form-basic.js.map +1 -1
- package/dist/examples/form-dropdown.js +1 -1
- package/dist/examples/form-dropdown.js.map +1 -1
- package/dist/examples/internal/custom-action-renderables.d.ts +70 -0
- package/dist/examples/internal/custom-action-renderables.d.ts.map +1 -0
- package/dist/examples/internal/custom-action-renderables.js +163 -0
- package/dist/examples/internal/custom-action-renderables.js.map +1 -0
- package/dist/examples/internal/custom-dropdown.d.ts +99 -0
- package/dist/examples/internal/custom-dropdown.d.ts.map +1 -0
- package/dist/examples/internal/custom-dropdown.js +270 -0
- package/dist/examples/internal/custom-dropdown.js.map +1 -0
- package/dist/examples/internal/custom-renderable-form.d.ts +43 -0
- package/dist/examples/internal/custom-renderable-form.d.ts.map +1 -0
- package/dist/examples/internal/custom-renderable-form.js +284 -0
- package/dist/examples/internal/custom-renderable-form.js.map +1 -0
- package/dist/examples/internal/custom-renderable-list-default-search.d.ts +2 -0
- package/dist/examples/internal/custom-renderable-list-default-search.d.ts.map +1 -0
- package/dist/examples/internal/custom-renderable-list-default-search.js +16 -0
- package/dist/examples/internal/custom-renderable-list-default-search.js.map +1 -0
- package/dist/examples/internal/custom-renderable-list-v2-default-search.d.ts +2 -0
- package/dist/examples/internal/custom-renderable-list-v2-default-search.d.ts.map +1 -0
- package/dist/examples/internal/custom-renderable-list-v2-default-search.js +24 -0
- package/dist/examples/internal/custom-renderable-list-v2-default-search.js.map +1 -0
- package/dist/examples/internal/custom-renderable-list-v2.d.ts +189 -0
- package/dist/examples/internal/custom-renderable-list-v2.d.ts.map +1 -0
- package/dist/examples/internal/custom-renderable-list-v2.js +708 -0
- package/dist/examples/internal/custom-renderable-list-v2.js.map +1 -0
- package/dist/examples/internal/custom-renderable-list.d.ts +72 -0
- package/dist/examples/internal/custom-renderable-list.d.ts.map +1 -0
- package/dist/examples/internal/custom-renderable-list.js +544 -0
- package/dist/examples/internal/custom-renderable-list.js.map +1 -0
- package/dist/examples/internal/rhf-custom-ref.js +5 -4
- package/dist/examples/internal/rhf-custom-ref.js.map +1 -1
- package/dist/examples/internal/scrollbox-with-descendants.js +4 -2
- package/dist/examples/internal/scrollbox-with-descendants.js.map +1 -1
- package/dist/examples/list-controlled-search.d.ts +2 -0
- package/dist/examples/list-controlled-search.d.ts.map +1 -0
- package/dist/examples/list-controlled-search.js +12 -0
- package/dist/examples/list-controlled-search.js.map +1 -0
- package/dist/examples/list-detail-metadata.js +1 -1
- package/dist/examples/list-detail-metadata.js.map +1 -1
- package/dist/examples/simple-image-mask.d.ts +8 -0
- package/dist/examples/simple-image-mask.d.ts.map +1 -0
- package/dist/examples/simple-image-mask.js +12 -0
- package/dist/examples/simple-image-mask.js.map +1 -0
- package/dist/examples/toast-variations.js +1 -1
- package/dist/examples/toast-variations.js.map +1 -1
- package/dist/extensions/dev.d.ts.map +1 -1
- package/dist/extensions/dev.js +3 -2
- package/dist/extensions/dev.js.map +1 -1
- package/dist/extensions/react-refresh-init.d.ts.map +1 -1
- package/dist/extensions/react-refresh-init.js +4 -3
- package/dist/extensions/react-refresh-init.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- 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 +2 -1
- package/dist/internal/date-picker-widget.js.map +1 -1
- package/dist/internal/dialog.d.ts +6 -0
- package/dist/internal/dialog.d.ts.map +1 -1
- package/dist/internal/dialog.js +59 -18
- package/dist/internal/dialog.js.map +1 -1
- package/dist/internal/navigation.d.ts.map +1 -1
- package/dist/internal/navigation.js +8 -1
- package/dist/internal/navigation.js.map +1 -1
- package/dist/internal/offscreen.d.ts +3 -0
- package/dist/internal/offscreen.d.ts.map +1 -1
- package/dist/internal/offscreen.js +5 -0
- package/dist/internal/offscreen.js.map +1 -1
- package/dist/internal/providers.d.ts.map +1 -1
- package/dist/internal/providers.js +20 -3
- package/dist/internal/providers.js.map +1 -1
- package/dist/internal/scrollbox.d.ts.map +1 -1
- package/dist/internal/scrollbox.js +3 -2
- package/dist/internal/scrollbox.js.map +1 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +4 -0
- package/dist/logger.js.map +1 -1
- package/dist/preload.js +5 -17
- package/dist/preload.js.map +1 -1
- package/dist/state.d.ts +4 -0
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +4 -0
- package/dist/state.js.map +1 -1
- package/dist/test-border-overlay.d.ts +2 -0
- package/dist/test-border-overlay.d.ts.map +1 -0
- package/dist/test-border-overlay.js +7 -0
- package/dist/test-border-overlay.js.map +1 -0
- package/dist/test-layout-2.d.ts +2 -0
- package/dist/test-layout-2.d.ts.map +1 -0
- package/dist/test-layout-2.js +5 -0
- package/dist/test-layout-2.js.map +1 -0
- package/dist/test-layout.d.ts +2 -0
- package/dist/test-layout.d.ts.map +1 -0
- package/dist/test-layout.js +7 -0
- package/dist/test-layout.js.map +1 -0
- package/dist/theme.d.ts +1 -2
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +5 -9
- package/dist/theme.js.map +1 -1
- package/dist/utils/run-command.d.ts +1 -1
- package/dist/utils/run-command.d.ts.map +1 -1
- package/dist/utils/run-command.js +27 -7
- package/dist/utils/run-command.js.map +1 -1
- package/dist/utils.d.ts +1 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +44 -23
- package/dist/utils.js.map +1 -1
- package/dist/watcher.d.ts.map +1 -1
- package/dist/watcher.js +24 -4
- package/dist/watcher.js.map +1 -1
- package/package.json +14 -12
- package/src/action-utils.tsx +10 -0
- package/src/apis/cache.test.ts +35 -3
- package/src/apis/cache.tsx +184 -59
- package/src/apis/clipboard.tsx +5 -0
- package/src/apis/oauth.tsx +33 -4
- package/src/build.tsx +35 -58
- package/src/cli.tsx +156 -134
- package/src/compile.tsx +6 -3
- package/src/compile.vitest.tsx +33 -15
- package/src/components/actions.tsx +230 -99
- package/src/components/alert.tsx +11 -10
- package/src/components/animation-tick.tsx +1 -1
- package/src/components/detail.tsx +56 -151
- package/src/components/dropdown.tsx +70 -36
- package/src/components/footer.tsx +58 -33
- package/src/components/form/checkbox.tsx +30 -32
- package/src/components/form/date-picker.tsx +27 -47
- package/src/components/form/description.tsx +19 -18
- package/src/components/form/dropdown.tsx +95 -103
- package/src/components/form/file-autocomplete.tsx +9 -8
- package/src/components/form/file-picker.tsx +46 -46
- package/src/components/form/form-end.tsx +6 -4
- package/src/components/form/index.tsx +38 -48
- package/src/components/form/password-field.tsx +25 -27
- package/src/components/form/separator.tsx +3 -2
- package/src/components/form/tagpicker.tsx +2 -1
- package/src/components/form/text-area.tsx +25 -30
- package/src/components/form/text-field.tsx +25 -27
- package/src/components/form/use-form-navigation.tsx +4 -5
- package/src/components/form/with-left-border.tsx +48 -10
- package/src/components/icon.tsx +69 -0
- package/src/components/image.tsx +60 -7
- package/src/components/list.tsx +270 -202
- package/src/components/loading-bar.tsx +4 -3
- package/src/components/metadata.tsx +217 -0
- package/src/components/theme-picker.tsx +3 -2
- package/src/examples/actions-context.tsx +63 -0
- package/src/examples/actions-context.vitest.tsx +110 -0
- package/src/examples/actions-dialog-layout.vitest.tsx +2 -1
- package/src/examples/file-autocomplete.vitest.tsx +15 -15
- package/src/examples/form-basic.tsx +12 -0
- package/src/examples/form-basic.vitest.tsx +74 -74
- package/src/examples/form-dropdown.tsx +8 -0
- package/src/examples/form-dropdown.vitest.tsx +364 -421
- package/src/examples/form-tagpicker.vitest.tsx +56 -54
- package/src/examples/github.vitest.tsx +252 -0
- package/src/examples/internal/rhf-custom-ref.tsx +16 -15
- package/src/examples/internal/scrollbox-with-descendants.tsx +4 -2
- package/src/examples/internal/simple-dialog.tsx +1 -1
- package/src/examples/internal/simple-scrollbox.vitest.tsx +14 -9
- package/src/examples/list-controlled-search.tsx +28 -0
- package/src/examples/list-controlled-search.vitest.tsx +49 -0
- package/src/examples/list-detail-metadata.tsx +8 -5
- package/src/examples/list-detail-metadata.vitest.tsx +22 -22
- package/src/examples/list-dropdown-default.vitest.tsx +12 -12
- package/src/examples/list-scrollbox.vitest.tsx +52 -38
- package/src/examples/list-with-detail.vitest.tsx +45 -41
- package/src/examples/list-with-dropdown.vitest.tsx +5 -5
- package/src/examples/list-with-sections.vitest.tsx +65 -12
- package/src/examples/list-with-toast.vitest.tsx +4 -4
- package/src/examples/simple-file-picker.vitest.tsx +12 -12
- package/src/examples/simple-grid.vitest.tsx +53 -53
- package/src/examples/simple-image-mask.tsx +58 -0
- package/src/examples/simple-navigation.vitest.tsx +19 -19
- package/src/examples/store.vitest.tsx +1 -1
- package/src/examples/swift-extension.vitest.tsx +4 -2
- package/src/examples/synonyms.vitest.tsx +31 -9
- package/src/examples/toast-action.vitest.tsx +8 -8
- package/src/examples/toast-variations.tsx +1 -1
- package/src/examples/toast-variations.vitest.tsx +69 -134
- package/src/extensions/dev.tsx +3 -2
- package/src/extensions/dev.vitest.tsx +65 -28
- package/src/extensions/react-refresh-init.tsx +4 -3
- package/src/index.tsx +3 -1
- package/src/internal/date-picker-widget.tsx +2 -1
- package/src/internal/dialog.tsx +100 -28
- package/src/internal/navigation.tsx +8 -1
- package/src/internal/offscreen.tsx +10 -0
- package/src/internal/providers.tsx +34 -8
- package/src/internal/scrollbox.tsx +4 -2
- package/src/logger.tsx +4 -0
- package/src/preload.tsx +5 -17
- package/src/state.tsx +12 -0
- package/src/theme.tsx +6 -9
- package/src/utils/run-command.tsx +32 -8
- package/src/utils.tsx +58 -23
- package/src/watcher.tsx +26 -6
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useState, useRef, useLayoutEffect } from 'react'
|
|
2
2
|
import { BoxRenderable } from '@opentui/core'
|
|
3
|
-
import {
|
|
3
|
+
import { useTheme } from 'termcast/src/theme'
|
|
4
4
|
import { useAnimationTick, TICK_DIVISORS } from 'termcast/src/components/animation-tick'
|
|
5
5
|
|
|
6
6
|
interface LoadingBarProps {
|
|
@@ -11,6 +11,7 @@ interface LoadingBarProps {
|
|
|
11
11
|
|
|
12
12
|
export function LoadingBar(props: LoadingBarProps): any {
|
|
13
13
|
let { title, isLoading = false, barLength: propBarLength } = props
|
|
14
|
+
const theme = useTheme()
|
|
14
15
|
const [calculatedBarLength, setCalculatedBarLength] = useState(
|
|
15
16
|
propBarLength || 0,
|
|
16
17
|
)
|
|
@@ -78,12 +79,12 @@ export function LoadingBar(props: LoadingBarProps): any {
|
|
|
78
79
|
const getCharacterColor = (index: number): string => {
|
|
79
80
|
if (!isLoading) {
|
|
80
81
|
// When not loading, use default theme colors
|
|
81
|
-
return index < title.length ?
|
|
82
|
+
return index < title.length ? theme.text : '#626262'
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
// Title text stays static when loading, only animate the bar
|
|
85
86
|
if (index < title.length) {
|
|
86
|
-
return
|
|
87
|
+
return theme.textMuted // Keep title text muted during loading
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
// Only animate the bar part
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Metadata components used by both Detail.Metadata and List.Item.Detail.Metadata
|
|
3
|
+
*
|
|
4
|
+
* Provides Label, Separator, Link, and TagList components with configurable styling
|
|
5
|
+
* via MetadataContext for different use cases (standalone Detail vs List detail panel).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React, { createContext, useContext, ReactNode } from 'react'
|
|
9
|
+
import { TextAttributes } from '@opentui/core'
|
|
10
|
+
import { useTheme } from 'termcast/src/theme'
|
|
11
|
+
import { Color, resolveColor } from 'termcast/src/colors'
|
|
12
|
+
import type { ImageLike } from 'termcast/src/components/image'
|
|
13
|
+
|
|
14
|
+
interface MetadataConfig {
|
|
15
|
+
/**
|
|
16
|
+
* Maximum text length before switching to column layout.
|
|
17
|
+
* Can be a number or a function that takes title length and returns max value length.
|
|
18
|
+
* This allows dynamic calculation based on terminal width and title length.
|
|
19
|
+
*/
|
|
20
|
+
maxValueLen: number | ((titleLen: number) => number)
|
|
21
|
+
/** Minimum width for title column in row layout (default: 12) */
|
|
22
|
+
titleMinWidth: number
|
|
23
|
+
/** Padding below each metadata item (default: 0.5) */
|
|
24
|
+
paddingBottom: number
|
|
25
|
+
/** Width of separator line (default: 17) */
|
|
26
|
+
separatorWidth: number
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const defaultConfig: MetadataConfig = {
|
|
30
|
+
maxValueLen: 20,
|
|
31
|
+
titleMinWidth: 12,
|
|
32
|
+
paddingBottom: 0.5,
|
|
33
|
+
separatorWidth: 17,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Helper to resolve maxValueLen - handles both number and function */
|
|
37
|
+
const resolveMaxValueLen = (config: MetadataConfig, titleLen: number): number => {
|
|
38
|
+
if (typeof config.maxValueLen === 'function') {
|
|
39
|
+
return config.maxValueLen(titleLen)
|
|
40
|
+
}
|
|
41
|
+
return config.maxValueLen
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const MetadataContext = createContext<MetadataConfig>(defaultConfig)
|
|
45
|
+
|
|
46
|
+
// Props types
|
|
47
|
+
interface MetadataProps {
|
|
48
|
+
children: ReactNode
|
|
49
|
+
/** Configuration for metadata display */
|
|
50
|
+
config?: Partial<MetadataConfig>
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface LabelProps {
|
|
54
|
+
title: string
|
|
55
|
+
icon?: ImageLike | undefined | null
|
|
56
|
+
text?:
|
|
57
|
+
| string
|
|
58
|
+
| {
|
|
59
|
+
value: string
|
|
60
|
+
color?: Color.ColorLike | null
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface SeparatorProps {}
|
|
65
|
+
|
|
66
|
+
interface LinkProps {
|
|
67
|
+
title: string
|
|
68
|
+
target: string
|
|
69
|
+
text: string
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface TagListProps {
|
|
73
|
+
title: string
|
|
74
|
+
children: ReactNode
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
interface TagListItemProps {
|
|
78
|
+
icon?: ImageLike | undefined | null
|
|
79
|
+
text?: string
|
|
80
|
+
color?: Color.ColorLike | undefined | null
|
|
81
|
+
onAction?: () => void
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Components
|
|
85
|
+
const MetadataLabel = (props: LabelProps): any => {
|
|
86
|
+
const theme = useTheme()
|
|
87
|
+
const config = useContext(MetadataContext)
|
|
88
|
+
const textValue = typeof props.text === 'string' ? props.text : props.text?.value
|
|
89
|
+
const textColor = typeof props.text === 'object' ? props.text?.color : undefined
|
|
90
|
+
|
|
91
|
+
// No text = header label (just title, no colon or dash)
|
|
92
|
+
if (!textValue) {
|
|
93
|
+
return (
|
|
94
|
+
<box style={{ paddingBottom: config.paddingBottom }}>
|
|
95
|
+
<text flexShrink={0} fg={theme.textMuted}>{props.title}</text>
|
|
96
|
+
</box>
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const maxLen = resolveMaxValueLen(config, props.title.length)
|
|
101
|
+
|
|
102
|
+
// Long value = column layout (title on one line, value below)
|
|
103
|
+
if (textValue.length > maxLen) {
|
|
104
|
+
return (
|
|
105
|
+
<box style={{ flexDirection: 'column', paddingBottom: config.paddingBottom }}>
|
|
106
|
+
<text flexShrink={0} fg={theme.textMuted}>{props.title}:</text>
|
|
107
|
+
<text flexShrink={0} fg={resolveColor(textColor) || theme.text}>{textValue}</text>
|
|
108
|
+
</box>
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Short value = row layout (title: value on same line)
|
|
113
|
+
return (
|
|
114
|
+
<box style={{ flexDirection: 'row', paddingBottom: config.paddingBottom }}>
|
|
115
|
+
<text flexShrink={0} fg={theme.textMuted} style={{ minWidth: config.titleMinWidth }}>{props.title}:</text>
|
|
116
|
+
<text flexShrink={0} fg={resolveColor(textColor) || theme.text}>{textValue}</text>
|
|
117
|
+
</box>
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const MetadataSeparator = (_props: SeparatorProps): any => {
|
|
122
|
+
const theme = useTheme()
|
|
123
|
+
const config = useContext(MetadataContext)
|
|
124
|
+
return (
|
|
125
|
+
<box style={{ paddingBottom: config.paddingBottom }}>
|
|
126
|
+
<text flexShrink={0} fg={theme.border}>{'─'.repeat(config.separatorWidth)}</text>
|
|
127
|
+
</box>
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const MetadataLink = (props: LinkProps): any => {
|
|
132
|
+
const theme = useTheme()
|
|
133
|
+
const config = useContext(MetadataContext)
|
|
134
|
+
const maxLen = resolveMaxValueLen(config, props.title.length)
|
|
135
|
+
const isLongValue = props.text.length > maxLen
|
|
136
|
+
|
|
137
|
+
if (isLongValue) {
|
|
138
|
+
return (
|
|
139
|
+
<box style={{ flexDirection: 'column', paddingBottom: config.paddingBottom }}>
|
|
140
|
+
<text flexShrink={0} fg={theme.textMuted}>{props.title}:</text>
|
|
141
|
+
<text flexShrink={0} fg={theme.accent} attributes={TextAttributes.UNDERLINE}>{props.text}</text>
|
|
142
|
+
</box>
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return (
|
|
147
|
+
<box style={{ flexDirection: 'row', paddingBottom: config.paddingBottom }}>
|
|
148
|
+
<text flexShrink={0} fg={theme.textMuted} style={{ minWidth: config.titleMinWidth }}>{props.title}:</text>
|
|
149
|
+
<text flexShrink={0} fg={theme.accent} attributes={TextAttributes.UNDERLINE}>{props.text}</text>
|
|
150
|
+
</box>
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const MetadataTagListItem = (props: TagListItemProps): any => {
|
|
155
|
+
const theme = useTheme()
|
|
156
|
+
const displayText = props.text || ''
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<text
|
|
160
|
+
fg={resolveColor(props.color) || theme.text}
|
|
161
|
+
style={{
|
|
162
|
+
paddingRight: 1,
|
|
163
|
+
paddingLeft: props.icon ? 1 : 0,
|
|
164
|
+
}}
|
|
165
|
+
>
|
|
166
|
+
{displayText}
|
|
167
|
+
</text>
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
interface MetadataTagListType {
|
|
172
|
+
(props: TagListProps): any
|
|
173
|
+
Item: (props: TagListItemProps) => any
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const MetadataTagList: MetadataTagListType = (props) => {
|
|
177
|
+
const theme = useTheme()
|
|
178
|
+
const config = useContext(MetadataContext)
|
|
179
|
+
|
|
180
|
+
return (
|
|
181
|
+
<box style={{ flexDirection: 'row', paddingBottom: config.paddingBottom }}>
|
|
182
|
+
<text flexShrink={0} fg={theme.textMuted} style={{ minWidth: config.titleMinWidth }}>{props.title}:</text>
|
|
183
|
+
<box style={{ flexDirection: 'row' }}>{props.children}</box>
|
|
184
|
+
</box>
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
MetadataTagList.Item = MetadataTagListItem
|
|
189
|
+
|
|
190
|
+
// Main Metadata component with compound components
|
|
191
|
+
interface MetadataType {
|
|
192
|
+
(props: MetadataProps): any
|
|
193
|
+
Label: (props: LabelProps) => any
|
|
194
|
+
Separator: (props: SeparatorProps) => any
|
|
195
|
+
Link: (props: LinkProps) => any
|
|
196
|
+
TagList: MetadataTagListType
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const Metadata: MetadataType = (props) => {
|
|
200
|
+
const config = { ...defaultConfig, ...props.config }
|
|
201
|
+
|
|
202
|
+
return (
|
|
203
|
+
<MetadataContext.Provider value={config}>
|
|
204
|
+
<box style={{ flexDirection: 'column' }}>
|
|
205
|
+
{props.children}
|
|
206
|
+
</box>
|
|
207
|
+
</MetadataContext.Provider>
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
Metadata.Label = MetadataLabel
|
|
212
|
+
Metadata.Separator = MetadataSeparator
|
|
213
|
+
Metadata.Link = MetadataLink
|
|
214
|
+
Metadata.TagList = MetadataTagList
|
|
215
|
+
|
|
216
|
+
export { Metadata, MetadataContext, defaultConfig }
|
|
217
|
+
export type { MetadataProps, MetadataConfig, LabelProps, SeparatorProps, LinkProps, TagListProps, TagListItemProps }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useState } from 'react'
|
|
2
2
|
import { useKeyboard } from '@opentui/react'
|
|
3
|
-
import {
|
|
3
|
+
import { useTheme, persistTheme } from 'termcast/src/theme'
|
|
4
4
|
import { themeNames } from '../themes'
|
|
5
5
|
import { useStore } from 'termcast/src/state'
|
|
6
6
|
import { useDialog } from 'termcast/src/internal/dialog'
|
|
@@ -8,6 +8,7 @@ import { useIsInFocus } from 'termcast/src/internal/focus-context'
|
|
|
8
8
|
import { Dropdown } from 'termcast/src/components/dropdown'
|
|
9
9
|
|
|
10
10
|
export function ThemePicker(): any {
|
|
11
|
+
const theme = useTheme()
|
|
11
12
|
const dialog = useDialog()
|
|
12
13
|
const inFocus = useIsInFocus()
|
|
13
14
|
const currentThemeName = useStore((state) => state.currentThemeName)
|
|
@@ -49,7 +50,7 @@ export function ThemePicker(): any {
|
|
|
49
50
|
key={name}
|
|
50
51
|
title={name}
|
|
51
52
|
value={name}
|
|
52
|
-
color={name === previousTheme ?
|
|
53
|
+
color={name === previousTheme ? theme.primary : undefined}
|
|
53
54
|
/>
|
|
54
55
|
))}
|
|
55
56
|
</Dropdown>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example validating that ActionPanel preserves React context through portals.
|
|
3
|
+
*
|
|
4
|
+
* A custom React context (CounterContext) provides a counter value. The
|
|
5
|
+
* CounterAction component reads from this context at render time and closes
|
|
6
|
+
* over it in its onAction callback. If the portal preserves context correctly,
|
|
7
|
+
* the toast will show the matching prop and context values.
|
|
8
|
+
*/
|
|
9
|
+
import React, { createContext, useContext, useState } from 'react'
|
|
10
|
+
import { List, ActionPanel, Action, showToast, Toast, renderWithProviders } from 'termcast'
|
|
11
|
+
|
|
12
|
+
const CounterContext = createContext(0)
|
|
13
|
+
|
|
14
|
+
// Action component that reads from context at render time
|
|
15
|
+
function CounterAction({ counter }: { counter: number }) {
|
|
16
|
+
// Read from context - this works because the portal preserves the
|
|
17
|
+
// React tree context from the source component
|
|
18
|
+
const contextValue = useContext(CounterContext)
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Action
|
|
22
|
+
title="Show Counter"
|
|
23
|
+
onAction={() => {
|
|
24
|
+
// Both the prop and context value should match
|
|
25
|
+
showToast({
|
|
26
|
+
title: `prop=${counter} ctx=${contextValue}`,
|
|
27
|
+
style: Toast.Style.Success,
|
|
28
|
+
})
|
|
29
|
+
}}
|
|
30
|
+
/>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function ActionsContextExample() {
|
|
35
|
+
const [counter, setCounter] = useState(42)
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<CounterContext.Provider value={counter}>
|
|
39
|
+
<List
|
|
40
|
+
navigationTitle="Context Test"
|
|
41
|
+
searchBarPlaceholder="Search..."
|
|
42
|
+
>
|
|
43
|
+
<List.Item
|
|
44
|
+
title={`Counter: ${counter}`}
|
|
45
|
+
subtitle="Press enter to show counter via action"
|
|
46
|
+
actions={
|
|
47
|
+
<ActionPanel>
|
|
48
|
+
<CounterAction counter={counter} />
|
|
49
|
+
<Action
|
|
50
|
+
title="Increment"
|
|
51
|
+
onAction={() => {
|
|
52
|
+
setCounter((c) => c + 1)
|
|
53
|
+
}}
|
|
54
|
+
/>
|
|
55
|
+
</ActionPanel>
|
|
56
|
+
}
|
|
57
|
+
/>
|
|
58
|
+
</List>
|
|
59
|
+
</CounterContext.Provider>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
await renderWithProviders(<ActionsContextExample />)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { test, expect, afterEach, beforeEach } from 'vitest'
|
|
2
|
+
import { launchTerminal, Session } from 'tuistory/src'
|
|
3
|
+
|
|
4
|
+
let session: Session
|
|
5
|
+
|
|
6
|
+
beforeEach(async () => {
|
|
7
|
+
session = await launchTerminal({
|
|
8
|
+
command: 'bun',
|
|
9
|
+
args: ['src/examples/actions-context.tsx'],
|
|
10
|
+
cols: 70,
|
|
11
|
+
rows: 20,
|
|
12
|
+
})
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
session?.close()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test('actions preserve React context through portal', async () => {
|
|
20
|
+
// Wait for list to render
|
|
21
|
+
await session.text({
|
|
22
|
+
waitFor: (text) => /Counter: 42/.test(text),
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const initial = await session.text()
|
|
26
|
+
expect(initial).toContain('Counter:')
|
|
27
|
+
|
|
28
|
+
// Open actions panel with Ctrl+K
|
|
29
|
+
await session.press(['ctrl', 'k'])
|
|
30
|
+
|
|
31
|
+
const actionsPanel = await session.text({
|
|
32
|
+
waitFor: (text) => /Show Counter/.test(text),
|
|
33
|
+
timeout: 5000,
|
|
34
|
+
})
|
|
35
|
+
// Verify the actions dialog is shown with our custom action
|
|
36
|
+
expect(actionsPanel).toContain('Show Counter')
|
|
37
|
+
expect(actionsPanel).toContain('Increment')
|
|
38
|
+
expect(actionsPanel).toMatchInlineSnapshot(`
|
|
39
|
+
"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
╭────────────────────────────────────────────────────────────────╮
|
|
43
|
+
│ │
|
|
44
|
+
│ Actions esc │
|
|
45
|
+
│ │
|
|
46
|
+
│ > Search actions... │
|
|
47
|
+
│ │
|
|
48
|
+
│ ›Show Counter │
|
|
49
|
+
│ Increment │
|
|
50
|
+
│ │
|
|
51
|
+
│ Settings │
|
|
52
|
+
│ Change Theme... │
|
|
53
|
+
│ See Console Logs │
|
|
54
|
+
│ │
|
|
55
|
+
│ │
|
|
56
|
+
│ ↵ select ↑↓ navigate │
|
|
57
|
+
│ │
|
|
58
|
+
╰────────────────────────────────────────────────────────────────╯
|
|
59
|
+
"
|
|
60
|
+
`)
|
|
61
|
+
|
|
62
|
+
// Select "Show Counter" (first action, already selected)
|
|
63
|
+
await session.press('return')
|
|
64
|
+
|
|
65
|
+
// Verify toast shows correct context value (prop=42 ctx=42)
|
|
66
|
+
const afterAction = await session.text({
|
|
67
|
+
waitFor: (text) => /prop=42 ctx=42/.test(text),
|
|
68
|
+
timeout: 5000,
|
|
69
|
+
})
|
|
70
|
+
expect(afterAction).toContain('prop=42')
|
|
71
|
+
}, 30000)
|
|
72
|
+
|
|
73
|
+
test('actions context stays fresh after state updates', async () => {
|
|
74
|
+
// Wait for list to render
|
|
75
|
+
await session.text({
|
|
76
|
+
waitFor: (text) => /Counter: 42/.test(text),
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
// Open actions, select "Increment" to change counter
|
|
80
|
+
await session.press(['ctrl', 'k'])
|
|
81
|
+
await session.text({
|
|
82
|
+
waitFor: (text) => /Increment/.test(text),
|
|
83
|
+
timeout: 5000,
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
// Navigate down to "Increment" action
|
|
87
|
+
await session.press('down')
|
|
88
|
+
await session.press('return')
|
|
89
|
+
|
|
90
|
+
// Wait for counter to update to 43
|
|
91
|
+
await session.text({
|
|
92
|
+
waitFor: (text) => /Counter: 43/.test(text),
|
|
93
|
+
timeout: 5000,
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
// Now open actions again and run "Show Counter"
|
|
97
|
+
await session.press(['ctrl', 'k'])
|
|
98
|
+
await session.text({
|
|
99
|
+
waitFor: (text) => /Show Counter/.test(text),
|
|
100
|
+
timeout: 5000,
|
|
101
|
+
})
|
|
102
|
+
await session.press('return')
|
|
103
|
+
|
|
104
|
+
// Verify toast shows UPDATED context value (43, not stale 42)
|
|
105
|
+
const afterAction = await session.text({
|
|
106
|
+
waitFor: (text) => /prop=43 ctx=43/.test(text),
|
|
107
|
+
timeout: 5000,
|
|
108
|
+
})
|
|
109
|
+
expect(afterAction).toContain('prop=43')
|
|
110
|
+
}, 30000)
|
|
@@ -17,7 +17,8 @@ afterEach(() => {
|
|
|
17
17
|
session?.close()
|
|
18
18
|
})
|
|
19
19
|
|
|
20
|
-
test
|
|
20
|
+
// this was a temporary test to find out if there was any layout shift in dialogs
|
|
21
|
+
test.skip('actions dialog layout shift when opening with ctrl+k', async () => {
|
|
21
22
|
// Wait for list to fully render
|
|
22
23
|
await session.text({
|
|
23
24
|
waitFor: (text) => /search/i.test(text) && /Apple/.test(text),
|
|
@@ -41,7 +41,6 @@ test('autocomplete shows flat file list in dialog', async () => {
|
|
|
41
41
|
│
|
|
42
42
|
◇ Select Files
|
|
43
43
|
│ Enter file path...
|
|
44
|
-
│
|
|
45
44
|
│ Choose one or more files to upload
|
|
46
45
|
│
|
|
47
46
|
╭────────────────────────────────────────────────────────────────╮
|
|
@@ -52,8 +51,6 @@ test('autocomplete shows flat file list in dialog', async () => {
|
|
|
52
51
|
│ │
|
|
53
52
|
│ ↑↓ navigate ⏎/tab select esc close │
|
|
54
53
|
╰────────────────────────────────────────────────────────────────╯
|
|
55
|
-
│ Choose exactly one file
|
|
56
|
-
│
|
|
57
54
|
└
|
|
58
55
|
|
|
59
56
|
|
|
@@ -61,6 +58,9 @@ test('autocomplete shows flat file list in dialog', async () => {
|
|
|
61
58
|
|
|
62
59
|
|
|
63
60
|
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
64
|
"
|
|
65
65
|
`)
|
|
66
66
|
}, 10000)
|
|
@@ -93,7 +93,6 @@ test('autocomplete navigation with down/up keys', async () => {
|
|
|
93
93
|
│
|
|
94
94
|
◇ Select Files
|
|
95
95
|
│ Enter file path...
|
|
96
|
-
│
|
|
97
96
|
│ Choose one or more files to upload
|
|
98
97
|
│
|
|
99
98
|
╭────────────────────────────────────────────────────────────────╮
|
|
@@ -104,8 +103,6 @@ test('autocomplete navigation with down/up keys', async () => {
|
|
|
104
103
|
│ │
|
|
105
104
|
│ ↑↓ navigate ⏎/tab select esc close │
|
|
106
105
|
╰────────────────────────────────────────────────────────────────╯
|
|
107
|
-
│ Choose exactly one file
|
|
108
|
-
│
|
|
109
106
|
└
|
|
110
107
|
|
|
111
108
|
|
|
@@ -113,6 +110,9 @@ test('autocomplete navigation with down/up keys', async () => {
|
|
|
113
110
|
|
|
114
111
|
|
|
115
112
|
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
|
|
116
116
|
"
|
|
117
117
|
`)
|
|
118
118
|
|
|
@@ -130,7 +130,6 @@ test('autocomplete navigation with down/up keys', async () => {
|
|
|
130
130
|
│
|
|
131
131
|
◇ Select Files
|
|
132
132
|
│ Enter file path...
|
|
133
|
-
│
|
|
134
133
|
│ Choose one or more files to upload
|
|
135
134
|
│
|
|
136
135
|
╭────────────────────────────────────────────────────────────────╮
|
|
@@ -141,8 +140,6 @@ test('autocomplete navigation with down/up keys', async () => {
|
|
|
141
140
|
│ │
|
|
142
141
|
│ ↑↓ navigate ⏎/tab select esc close │
|
|
143
142
|
╰────────────────────────────────────────────────────────────────╯
|
|
144
|
-
│ Choose exactly one file
|
|
145
|
-
│
|
|
146
143
|
└
|
|
147
144
|
|
|
148
145
|
|
|
@@ -150,6 +147,9 @@ test('autocomplete navigation with down/up keys', async () => {
|
|
|
150
147
|
|
|
151
148
|
|
|
152
149
|
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
|
|
153
153
|
"
|
|
154
154
|
`)
|
|
155
155
|
}, 10000)
|
|
@@ -179,8 +179,6 @@ test('file picker shows only files, not folders', async () => {
|
|
|
179
179
|
│ John Doe
|
|
180
180
|
│
|
|
181
181
|
◆ Select Files
|
|
182
|
-
│ t
|
|
183
|
-
│
|
|
184
182
|
╭────────────────────────────────────────────────────────────────╮
|
|
185
183
|
│ │
|
|
186
184
|
│ Filter: t │
|
|
@@ -195,11 +193,13 @@ test('file picker shows only files, not folders', async () => {
|
|
|
195
193
|
│ ↑↓ navigate ⏎/tab select esc close │
|
|
196
194
|
╰────────────────────────────────────────────────────────────────╯
|
|
197
195
|
|
|
198
|
-
|
|
199
196
|
ctrl ↵ submit tab navigate ^k actions
|
|
200
197
|
|
|
201
198
|
|
|
202
199
|
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
|
|
203
203
|
"
|
|
204
204
|
`)
|
|
205
205
|
expect(snapshot).toContain('▪')
|
|
@@ -234,17 +234,14 @@ test('escape closes autocomplete and form stays visible', async () => {
|
|
|
234
234
|
│
|
|
235
235
|
◇ Select Files
|
|
236
236
|
│ Enter file path...
|
|
237
|
-
│
|
|
238
237
|
│ Choose one or more files to upload
|
|
239
238
|
│
|
|
240
239
|
◆ Select Folder
|
|
241
240
|
│ s
|
|
242
|
-
│
|
|
243
241
|
│ Choose a folder for output
|
|
244
242
|
│
|
|
245
243
|
◇ Select Single File
|
|
246
244
|
│ Enter file path...
|
|
247
|
-
│
|
|
248
245
|
│ Choose exactly one file
|
|
249
246
|
│
|
|
250
247
|
└
|
|
@@ -254,6 +251,9 @@ test('escape closes autocomplete and form stays visible', async () => {
|
|
|
254
251
|
|
|
255
252
|
|
|
256
253
|
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
|
|
257
257
|
"
|
|
258
258
|
`)
|
|
259
259
|
}, 10000)
|
|
@@ -82,6 +82,18 @@ export function FormBasicExample(): any {
|
|
|
82
82
|
</Form.Dropdown.Section>
|
|
83
83
|
</Form.Dropdown>
|
|
84
84
|
|
|
85
|
+
<Form.Dropdown
|
|
86
|
+
id='emptyDropdown'
|
|
87
|
+
title='Empty Dropdown'
|
|
88
|
+
placeholder='No items available'
|
|
89
|
+
/>
|
|
90
|
+
|
|
91
|
+
<Form.TextField
|
|
92
|
+
id='minimalField'
|
|
93
|
+
title='Minimal Field'
|
|
94
|
+
placeholder='No info text'
|
|
95
|
+
/>
|
|
96
|
+
|
|
85
97
|
<Form.DatePicker
|
|
86
98
|
id='birthdate'
|
|
87
99
|
title='Date of Birth'
|