mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-21 15:55:50 +00:00
更新大量 tsx 原始文件; 已经迁移 login panel; 部分 (#121)
* style(B1-1): 格式化 ink/buddy/cli/context/screens/tasks/services/keybindings/state (43 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 修复了 Box.tsx 和 ScrollBox.tsx 中无效的 global.d.ts import。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-2): 格式化 commands (79 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-3): 格式化 components/messages,permissions,mcp,sandbox,shell (104 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-4): 格式化 components/PromptInput,FeedbackSurvey,tasks,agents,skills,design-system,wizard (73 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-5): 格式化 components其余 + hooks + tools (232 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(B1-6): 格式化 main/entrypoints/utils/moreright (21 files) 纯格式化:移除分号、React Compiler import、import 多行展开。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: 更新 README,新增 Run.ps1/TODO.md,删除 V6.md - README.md: 大幅重写,更详细版本历史和配置示例 - Run.ps1: 新增 Windows 启动脚本 - TODO.md: 新增包完成清单 - V6.md: 删除(架构重构规划已不适用) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: 修复以前的问题 * fix: 修复 login 面板的问题 --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,70 +1,73 @@
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import * as React from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useSearchInput } from '../../hooks/useSearchInput.js';
|
||||
import { useTerminalSize } from '../../hooks/useTerminalSize.js';
|
||||
import type { KeyboardEvent } from '../../ink/events/keyboard-event.js';
|
||||
import { clamp } from '../../ink/layout/geometry.js';
|
||||
import { Box, Text, useTerminalFocus } from '../../ink.js';
|
||||
import { SearchBox } from '../SearchBox.js';
|
||||
import { Byline } from './Byline.js';
|
||||
import { KeyboardShortcutHint } from './KeyboardShortcutHint.js';
|
||||
import { ListItem } from './ListItem.js';
|
||||
import { Pane } from './Pane.js';
|
||||
import * as React from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useSearchInput } from '../../hooks/useSearchInput.js'
|
||||
import { useTerminalSize } from '../../hooks/useTerminalSize.js'
|
||||
import type { KeyboardEvent } from '../../ink/events/keyboard-event.js'
|
||||
import { clamp } from '../../ink/layout/geometry.js'
|
||||
import { Box, Text, useTerminalFocus } from '../../ink.js'
|
||||
import { SearchBox } from '../SearchBox.js'
|
||||
import { Byline } from './Byline.js'
|
||||
import { KeyboardShortcutHint } from './KeyboardShortcutHint.js'
|
||||
import { ListItem } from './ListItem.js'
|
||||
import { Pane } from './Pane.js'
|
||||
|
||||
type PickerAction<T> = {
|
||||
/** Hint label shown in the byline, e.g. "mention" → "Tab to mention". */
|
||||
action: string;
|
||||
handler: (item: T) => void;
|
||||
};
|
||||
action: string
|
||||
handler: (item: T) => void
|
||||
}
|
||||
|
||||
type Props<T> = {
|
||||
title: string;
|
||||
placeholder?: string;
|
||||
initialQuery?: string;
|
||||
items: readonly T[];
|
||||
getKey: (item: T) => string;
|
||||
title: string
|
||||
placeholder?: string
|
||||
initialQuery?: string
|
||||
items: readonly T[]
|
||||
getKey: (item: T) => string
|
||||
/** Keep to one line — preview handles overflow. */
|
||||
renderItem: (item: T, isFocused: boolean) => React.ReactNode;
|
||||
renderPreview?: (item: T) => React.ReactNode;
|
||||
renderItem: (item: T, isFocused: boolean) => React.ReactNode
|
||||
renderPreview?: (item: T) => React.ReactNode
|
||||
/** 'right' keeps hints stable (no bounce), but needs width. */
|
||||
previewPosition?: 'bottom' | 'right';
|
||||
visibleCount?: number;
|
||||
previewPosition?: 'bottom' | 'right'
|
||||
visibleCount?: number
|
||||
/**
|
||||
* 'up' puts items[0] at the bottom next to the input (atuin-style). Arrows
|
||||
* always match screen direction — ↑ walks visually up regardless.
|
||||
*/
|
||||
direction?: 'down' | 'up';
|
||||
direction?: 'down' | 'up'
|
||||
/** Caller owns filtering: re-filter on each call and pass new items. */
|
||||
onQueryChange: (query: string) => void;
|
||||
onQueryChange: (query: string) => void
|
||||
/** Enter key. Primary action. */
|
||||
onSelect: (item: T) => void;
|
||||
onSelect: (item: T) => void
|
||||
/**
|
||||
* Tab key. If provided, Tab no longer aliases Enter — it gets its own
|
||||
* handler and hint. Shift+Tab falls through to this if onShiftTab is unset.
|
||||
*/
|
||||
onTab?: PickerAction<T>;
|
||||
onTab?: PickerAction<T>
|
||||
/** Shift+Tab key. Gets its own hint. */
|
||||
onShiftTab?: PickerAction<T>;
|
||||
onShiftTab?: PickerAction<T>
|
||||
/**
|
||||
* Fires when the focused item changes (via arrows or when items reset).
|
||||
* Useful for async preview loading — keeps I/O out of renderPreview.
|
||||
*/
|
||||
onFocus?: (item: T | undefined) => void;
|
||||
onCancel: () => void;
|
||||
onFocus?: (item: T | undefined) => void
|
||||
onCancel: () => void
|
||||
/** Shown when items is empty. Caller bakes loading/searching state into this. */
|
||||
emptyMessage?: string | ((query: string) => string);
|
||||
emptyMessage?: string | ((query: string) => string)
|
||||
/**
|
||||
* Status line below the list, e.g. "500+ matches" or "42 matches…".
|
||||
* Caller decides when to show it — pass undefined to hide.
|
||||
*/
|
||||
matchLabel?: string;
|
||||
selectAction?: string;
|
||||
extraHints?: React.ReactNode;
|
||||
};
|
||||
const DEFAULT_VISIBLE = 8;
|
||||
matchLabel?: string
|
||||
selectAction?: string
|
||||
extraHints?: React.ReactNode
|
||||
}
|
||||
|
||||
const DEFAULT_VISIBLE = 8
|
||||
// Pane (paddingTop + Divider) + title + 3 gaps + SearchBox (rounded border = 3
|
||||
// rows) + hints. matchLabel adds +1 when present, accounted for separately.
|
||||
const CHROME_ROWS = 10;
|
||||
const MIN_VISIBLE = 2;
|
||||
const CHROME_ROWS = 10
|
||||
const MIN_VISIBLE = 2
|
||||
|
||||
export function FuzzyPicker<T>({
|
||||
title,
|
||||
placeholder = 'Type to search…',
|
||||
@@ -85,117 +88,168 @@ export function FuzzyPicker<T>({
|
||||
emptyMessage = 'No results',
|
||||
matchLabel,
|
||||
selectAction = 'select',
|
||||
extraHints
|
||||
extraHints,
|
||||
}: Props<T>): React.ReactNode {
|
||||
const isTerminalFocused = useTerminalFocus();
|
||||
const {
|
||||
rows,
|
||||
columns
|
||||
} = useTerminalSize();
|
||||
const [focusedIndex, setFocusedIndex] = useState(0);
|
||||
const isTerminalFocused = useTerminalFocus()
|
||||
const { rows, columns } = useTerminalSize()
|
||||
const [focusedIndex, setFocusedIndex] = useState(0)
|
||||
|
||||
// Cap visibleCount so the picker never exceeds the terminal height. When it
|
||||
// overflows, each re-render (arrow key, ctrl+p) mis-positions the cursor-up
|
||||
// by the overflow amount and a previously-drawn line flashes blank.
|
||||
const visibleCount = Math.max(MIN_VISIBLE, Math.min(requestedVisible, rows - CHROME_ROWS - (matchLabel ? 1 : 0)));
|
||||
const visibleCount = Math.max(
|
||||
MIN_VISIBLE,
|
||||
Math.min(requestedVisible, rows - CHROME_ROWS - (matchLabel ? 1 : 0)),
|
||||
)
|
||||
|
||||
// Full hint row with onTab+onShiftTab is ~100 chars and wraps inconsistently
|
||||
// below that. Compact mode drops shift+tab and shortens labels.
|
||||
const compact = columns < 120;
|
||||
const compact = columns < 120
|
||||
|
||||
const step = (delta: 1 | -1) => {
|
||||
setFocusedIndex(i => clamp(i + delta, 0, items.length - 1));
|
||||
};
|
||||
setFocusedIndex(i => clamp(i + delta, 0, items.length - 1))
|
||||
}
|
||||
|
||||
// onKeyDown fires after useSearchInput's useInput, so onExit must be a
|
||||
// no-op — return/downArrow are handled by handleKeyDown below. onCancel
|
||||
// still covers escape/ctrl+c/ctrl+d. Backspace-on-empty is disabled so
|
||||
// a held backspace doesn't eject the user from the dialog.
|
||||
const {
|
||||
query,
|
||||
cursorOffset
|
||||
} = useSearchInput({
|
||||
const { query, cursorOffset } = useSearchInput({
|
||||
isActive: true,
|
||||
onExit: () => {},
|
||||
onCancel,
|
||||
initialQuery,
|
||||
backspaceExitsOnEmpty: false
|
||||
});
|
||||
backspaceExitsOnEmpty: false,
|
||||
})
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'up' || e.ctrl && e.key === 'p') {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
step(direction === 'up' ? 1 : -1);
|
||||
return;
|
||||
if (e.key === 'up' || (e.ctrl && e.key === 'p')) {
|
||||
e.preventDefault()
|
||||
e.stopImmediatePropagation()
|
||||
step(direction === 'up' ? 1 : -1)
|
||||
return
|
||||
}
|
||||
if (e.key === 'down' || e.ctrl && e.key === 'n') {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
step(direction === 'up' ? -1 : 1);
|
||||
return;
|
||||
if (e.key === 'down' || (e.ctrl && e.key === 'n')) {
|
||||
e.preventDefault()
|
||||
e.stopImmediatePropagation()
|
||||
step(direction === 'up' ? -1 : 1)
|
||||
return
|
||||
}
|
||||
if (e.key === 'return') {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
const selected = items[focusedIndex];
|
||||
if (selected) onSelect(selected);
|
||||
return;
|
||||
e.preventDefault()
|
||||
e.stopImmediatePropagation()
|
||||
const selected = items[focusedIndex]
|
||||
if (selected) onSelect(selected)
|
||||
return
|
||||
}
|
||||
if (e.key === 'tab') {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
const selected = items[focusedIndex];
|
||||
if (!selected) return;
|
||||
const tabAction = e.shift ? onShiftTab ?? onTab : onTab;
|
||||
e.preventDefault()
|
||||
e.stopImmediatePropagation()
|
||||
const selected = items[focusedIndex]
|
||||
if (!selected) return
|
||||
const tabAction = e.shift ? (onShiftTab ?? onTab) : onTab
|
||||
if (tabAction) {
|
||||
tabAction.handler(selected);
|
||||
tabAction.handler(selected)
|
||||
} else {
|
||||
onSelect(selected);
|
||||
onSelect(selected)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
onQueryChange(query);
|
||||
setFocusedIndex(0);
|
||||
onQueryChange(query)
|
||||
setFocusedIndex(0)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [query]);
|
||||
}, [query])
|
||||
|
||||
useEffect(() => {
|
||||
setFocusedIndex(i => clamp(i, 0, items.length - 1));
|
||||
}, [items.length]);
|
||||
const focused = items[focusedIndex];
|
||||
setFocusedIndex(i => clamp(i, 0, items.length - 1))
|
||||
}, [items.length])
|
||||
|
||||
const focused = items[focusedIndex]
|
||||
useEffect(() => {
|
||||
onFocus?.(focused);
|
||||
onFocus?.(focused)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [focused]);
|
||||
const windowStart = clamp(focusedIndex - visibleCount + 1, 0, items.length - visibleCount);
|
||||
const visible = items.slice(windowStart, windowStart + visibleCount);
|
||||
const emptyText = typeof emptyMessage === 'function' ? emptyMessage(query) : emptyMessage;
|
||||
const searchBox = <SearchBox query={query} cursorOffset={cursorOffset} placeholder={placeholder} isFocused isTerminalFocused={isTerminalFocused} />;
|
||||
const listBlock = <List visible={visible} windowStart={windowStart} visibleCount={visibleCount} total={items.length} focusedIndex={focusedIndex} direction={direction} getKey={getKey} renderItem={renderItem} emptyText={emptyText} />;
|
||||
const preview = renderPreview && focused ? <Box flexDirection="column" flexGrow={1}>
|
||||
}, [focused])
|
||||
|
||||
const windowStart = clamp(
|
||||
focusedIndex - visibleCount + 1,
|
||||
0,
|
||||
items.length - visibleCount,
|
||||
)
|
||||
const visible = items.slice(windowStart, windowStart + visibleCount)
|
||||
|
||||
const emptyText =
|
||||
typeof emptyMessage === 'function' ? emptyMessage(query) : emptyMessage
|
||||
|
||||
const searchBox = (
|
||||
<SearchBox
|
||||
query={query}
|
||||
cursorOffset={cursorOffset}
|
||||
placeholder={placeholder}
|
||||
isFocused
|
||||
isTerminalFocused={isTerminalFocused}
|
||||
/>
|
||||
)
|
||||
|
||||
const listBlock = (
|
||||
<List
|
||||
visible={visible}
|
||||
windowStart={windowStart}
|
||||
visibleCount={visibleCount}
|
||||
total={items.length}
|
||||
focusedIndex={focusedIndex}
|
||||
direction={direction}
|
||||
getKey={getKey}
|
||||
renderItem={renderItem}
|
||||
emptyText={emptyText}
|
||||
/>
|
||||
)
|
||||
|
||||
const preview =
|
||||
renderPreview && focused ? (
|
||||
<Box flexDirection="column" flexGrow={1}>
|
||||
{renderPreview(focused)}
|
||||
</Box> : null;
|
||||
</Box>
|
||||
) : null
|
||||
|
||||
// Structure must not depend on preview truthiness — when focused goes
|
||||
// undefined (e.g. delete clears matches), switching row→fragment would
|
||||
// change both layout AND gap count, bouncing the searchBox below.
|
||||
const listGroup = renderPreview && previewPosition === 'right' ? <Box flexDirection="row" gap={2} height={visibleCount + (matchLabel ? 1 : 0)}>
|
||||
const listGroup =
|
||||
renderPreview && previewPosition === 'right' ? (
|
||||
<Box
|
||||
flexDirection="row"
|
||||
gap={2}
|
||||
height={visibleCount + (matchLabel ? 1 : 0)}
|
||||
>
|
||||
<Box flexDirection="column" flexShrink={0}>
|
||||
{listBlock}
|
||||
{matchLabel && <Text dimColor>{matchLabel}</Text>}
|
||||
</Box>
|
||||
{preview ?? <Box flexGrow={1} />}
|
||||
</Box> :
|
||||
// Box (not fragment) so the outer gap={1} doesn't insert a blank line
|
||||
// between list/matchLabel/preview — that read as extra space above the
|
||||
// prompt in direction='up'.
|
||||
<Box flexDirection="column">
|
||||
</Box>
|
||||
) : (
|
||||
// Box (not fragment) so the outer gap={1} doesn't insert a blank line
|
||||
// between list/matchLabel/preview — that read as extra space above the
|
||||
// prompt in direction='up'.
|
||||
<Box flexDirection="column">
|
||||
{listBlock}
|
||||
{matchLabel && <Text dimColor>{matchLabel}</Text>}
|
||||
{preview}
|
||||
</Box>;
|
||||
const inputAbove = direction !== 'up';
|
||||
return <Pane color="permission">
|
||||
<Box flexDirection="column" gap={1} tabIndex={0} autoFocus onKeyDown={handleKeyDown}>
|
||||
</Box>
|
||||
)
|
||||
|
||||
const inputAbove = direction !== 'up'
|
||||
return (
|
||||
<Pane color="permission">
|
||||
<Box
|
||||
flexDirection="column"
|
||||
gap={1}
|
||||
tabIndex={0}
|
||||
autoFocus
|
||||
onKeyDown={handleKeyDown}
|
||||
>
|
||||
<Text bold color="permission">
|
||||
{title}
|
||||
</Text>
|
||||
@@ -204,108 +258,93 @@ export function FuzzyPicker<T>({
|
||||
{!inputAbove && searchBox}
|
||||
<Text dimColor>
|
||||
<Byline>
|
||||
<KeyboardShortcutHint shortcut="↑/↓" action={compact ? 'nav' : 'navigate'} />
|
||||
<KeyboardShortcutHint shortcut="Enter" action={compact ? firstWord(selectAction) : selectAction} />
|
||||
{onTab && <KeyboardShortcutHint shortcut="Tab" action={onTab.action} />}
|
||||
{onShiftTab && !compact && <KeyboardShortcutHint shortcut="shift+tab" action={onShiftTab.action} />}
|
||||
<KeyboardShortcutHint
|
||||
shortcut="↑/↓"
|
||||
action={compact ? 'nav' : 'navigate'}
|
||||
/>
|
||||
<KeyboardShortcutHint
|
||||
shortcut="Enter"
|
||||
action={compact ? firstWord(selectAction) : selectAction}
|
||||
/>
|
||||
{onTab && (
|
||||
<KeyboardShortcutHint shortcut="Tab" action={onTab.action} />
|
||||
)}
|
||||
{onShiftTab && !compact && (
|
||||
<KeyboardShortcutHint
|
||||
shortcut="shift+tab"
|
||||
action={onShiftTab.action}
|
||||
/>
|
||||
)}
|
||||
<KeyboardShortcutHint shortcut="Esc" action="cancel" />
|
||||
{extraHints}
|
||||
</Byline>
|
||||
</Text>
|
||||
</Box>
|
||||
</Pane>;
|
||||
</Pane>
|
||||
)
|
||||
}
|
||||
type ListProps<T> = Pick<Props<T>, 'visibleCount' | 'direction' | 'getKey' | 'renderItem'> & {
|
||||
visible: readonly T[];
|
||||
windowStart: number;
|
||||
total: number;
|
||||
focusedIndex: number;
|
||||
emptyText: string;
|
||||
};
|
||||
function List(t0) {
|
||||
const $ = _c(27);
|
||||
const {
|
||||
visible,
|
||||
windowStart,
|
||||
visibleCount,
|
||||
total,
|
||||
focusedIndex,
|
||||
direction,
|
||||
getKey,
|
||||
renderItem,
|
||||
emptyText
|
||||
} = t0;
|
||||
|
||||
type ListProps<T> = Pick<
|
||||
Props<T>,
|
||||
'visibleCount' | 'direction' | 'getKey' | 'renderItem'
|
||||
> & {
|
||||
visible: readonly T[]
|
||||
windowStart: number
|
||||
total: number
|
||||
focusedIndex: number
|
||||
emptyText: string
|
||||
}
|
||||
|
||||
function List<T>({
|
||||
visible,
|
||||
windowStart,
|
||||
visibleCount,
|
||||
total,
|
||||
focusedIndex,
|
||||
direction,
|
||||
getKey,
|
||||
renderItem,
|
||||
emptyText,
|
||||
}: ListProps<T>): React.ReactNode {
|
||||
if (visible.length === 0) {
|
||||
let t1;
|
||||
if ($[0] !== emptyText) {
|
||||
t1 = <Text dimColor={true}>{emptyText}</Text>;
|
||||
$[0] = emptyText;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
let t2;
|
||||
if ($[2] !== t1 || $[3] !== visibleCount) {
|
||||
t2 = <Box height={visibleCount} flexShrink={0}>{t1}</Box>;
|
||||
$[2] = t1;
|
||||
$[3] = visibleCount;
|
||||
$[4] = t2;
|
||||
} else {
|
||||
t2 = $[4];
|
||||
}
|
||||
return t2;
|
||||
return (
|
||||
<Box height={visibleCount} flexShrink={0}>
|
||||
<Text dimColor>{emptyText}</Text>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
let t1;
|
||||
if ($[5] !== direction || $[6] !== focusedIndex || $[7] !== getKey || $[8] !== renderItem || $[9] !== total || $[10] !== visible || $[11] !== visibleCount || $[12] !== windowStart) {
|
||||
let t2;
|
||||
if ($[14] !== direction || $[15] !== focusedIndex || $[16] !== getKey || $[17] !== renderItem || $[18] !== total || $[19] !== visible.length || $[20] !== visibleCount || $[21] !== windowStart) {
|
||||
t2 = (item, i) => {
|
||||
const actualIndex = windowStart + i;
|
||||
const isFocused = actualIndex === focusedIndex;
|
||||
const atLowEdge = i === 0 && windowStart > 0;
|
||||
const atHighEdge = i === visible.length - 1 && windowStart + visibleCount < total;
|
||||
return <ListItem key={getKey(item)} isFocused={isFocused} showScrollUp={direction === "up" ? atHighEdge : atLowEdge} showScrollDown={direction === "up" ? atLowEdge : atHighEdge} styled={false}>{renderItem(item, isFocused)}</ListItem>;
|
||||
};
|
||||
$[14] = direction;
|
||||
$[15] = focusedIndex;
|
||||
$[16] = getKey;
|
||||
$[17] = renderItem;
|
||||
$[18] = total;
|
||||
$[19] = visible.length;
|
||||
$[20] = visibleCount;
|
||||
$[21] = windowStart;
|
||||
$[22] = t2;
|
||||
} else {
|
||||
t2 = $[22];
|
||||
}
|
||||
t1 = visible.map(t2);
|
||||
$[5] = direction;
|
||||
$[6] = focusedIndex;
|
||||
$[7] = getKey;
|
||||
$[8] = renderItem;
|
||||
$[9] = total;
|
||||
$[10] = visible;
|
||||
$[11] = visibleCount;
|
||||
$[12] = windowStart;
|
||||
$[13] = t1;
|
||||
} else {
|
||||
t1 = $[13];
|
||||
}
|
||||
const rows = t1;
|
||||
const t2 = direction === "up" ? "column-reverse" : "column";
|
||||
let t3;
|
||||
if ($[23] !== rows || $[24] !== t2 || $[25] !== visibleCount) {
|
||||
t3 = <Box height={visibleCount} flexShrink={0} flexDirection={t2}>{rows}</Box>;
|
||||
$[23] = rows;
|
||||
$[24] = t2;
|
||||
$[25] = visibleCount;
|
||||
$[26] = t3;
|
||||
} else {
|
||||
t3 = $[26];
|
||||
}
|
||||
return t3;
|
||||
|
||||
const rows = visible.map((item, i) => {
|
||||
const actualIndex = windowStart + i
|
||||
const isFocused = actualIndex === focusedIndex
|
||||
const atLowEdge = i === 0 && windowStart > 0
|
||||
const atHighEdge =
|
||||
i === visible.length - 1 && windowStart + visibleCount! < total
|
||||
return (
|
||||
<ListItem
|
||||
key={getKey(item)}
|
||||
isFocused={isFocused}
|
||||
showScrollUp={direction === 'up' ? atHighEdge : atLowEdge}
|
||||
showScrollDown={direction === 'up' ? atLowEdge : atHighEdge}
|
||||
styled={false}
|
||||
>
|
||||
{renderItem(item, isFocused)}
|
||||
</ListItem>
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<Box
|
||||
height={visibleCount}
|
||||
flexShrink={0}
|
||||
flexDirection={direction === 'up' ? 'column-reverse' : 'column'}
|
||||
>
|
||||
{rows}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
function firstWord(s: string): string {
|
||||
const i = s.indexOf(' ');
|
||||
return i === -1 ? s : s.slice(0, i);
|
||||
const i = s.indexOf(' ')
|
||||
return i === -1 ? s : s.slice(0, i)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user