Skip to content

Commit 705b724

Browse files
committed
Introduce use_is_mac hook to detect macOS and update UI components to use platform-specific keyboard shortcuts ⌘/Ctrl in dropdown menus and headers
1 parent b1c7a68 commit 705b724

15 files changed

Lines changed: 216 additions & 69 deletions

File tree

‎packages/shared/src/hooks/index.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from './use-is-narrow-viewport'
2+
export * from './use-is-mac'
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { useMemo } from 'react'
2+
3+
export const use_is_mac = (): boolean => {
4+
return useMemo(() => {
5+
if (typeof navigator == 'undefined') return false
6+
const any_nav = navigator as any
7+
const uach_platform: string | undefined = any_nav?.userAgentData?.platform
8+
if (typeof uach_platform == 'string') {
9+
return uach_platform.toLowerCase().includes('mac')
10+
}
11+
if (typeof navigator.userAgent == 'string') {
12+
return /mac/i.test(navigator.userAgent)
13+
}
14+
return false
15+
}, [])
16+
}

‎packages/ui/src/components/editor/common/DropdownMenu/DropdownMenu.module.scss‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,7 @@
4242
color: var(--vscode-descriptionForeground);
4343
font-size: 12px;
4444
}
45+
46+
.underlined {
47+
text-decoration: underline;
48+
}

‎packages/ui/src/components/editor/common/DropdownMenu/DropdownMenu.tsx‎

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export namespace DropdownMenu {
1111

1212
export type Props = {
1313
items: Item[]
14+
underline_non_selected_items?: boolean
1415
}
1516
}
1617

@@ -21,22 +22,39 @@ export const DropdownMenu: React.FC<DropdownMenu.Props> = (props) => {
2122
return (
2223
<div className={styles.menu}>
2324
<div className={styles.menu__inner}>
24-
{props.items.map((item, index) => (
25-
<div
26-
key={index}
27-
className={styles.item}
28-
onClick={item.on_click}
29-
data-selected={item.is_selected && is_preselection_respected}
30-
onMouseEnter={() => {
31-
set_is_preselection_respected(false)
32-
}}
33-
>
34-
<span>{item.label}</span>
35-
{item.shortcut && (
36-
<span className={styles.shortcut}>{item.shortcut}</span>
37-
)}
38-
</div>
39-
))}
25+
{props.items.map((item, index) => {
26+
const is_selected = item.is_selected && is_preselection_respected
27+
const should_underline =
28+
props.underline_non_selected_items && !is_selected
29+
30+
return (
31+
<div
32+
key={index}
33+
className={styles.item}
34+
onClick={item.on_click}
35+
data-selected={is_selected}
36+
onMouseEnter={() => {
37+
set_is_preselection_respected(false)
38+
}}
39+
>
40+
<span>
41+
{should_underline ? (
42+
<>
43+
<span className={styles.underlined}>
44+
{item.label.substring(0, 1)}
45+
</span>
46+
{item.label.substring(1)}
47+
</>
48+
) : (
49+
item.label
50+
)}
51+
</span>
52+
{item.shortcut && (
53+
<span className={styles.shortcut}>{item.shortcut}</span>
54+
)}
55+
</div>
56+
)
57+
})}
4058
</div>
4159
</div>
4260
)

‎packages/ui/src/components/editor/panel/Dropdown/Dropdown.tsx‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,21 @@ export const Dropdown = <T extends string>(props: Dropdown.Props<T>) => {
2323
const [is_open, set_is_open] = useState(false)
2424
const [just_opened, set_just_opened] = useState(false)
2525
const container_ref = useRef<HTMLDivElement>(null)
26+
const opened_by_shortcut = useRef(false)
2627

2728
const selected_option = props.options.find(
2829
(option) => option.value == props.selected_value
2930
)
3031

3132
const handle_toggle = () => {
33+
opened_by_shortcut.current = false
3234
set_is_open(!is_open)
3335
set_just_opened(!is_open)
3436
}
3537

3638
const handle_select = (value: T) => {
3739
props.on_change(value)
40+
opened_by_shortcut.current = false
3841
set_is_open(false)
3942
set_just_opened(false)
4043
}
@@ -63,6 +66,7 @@ export const Dropdown = <T extends string>(props: Dropdown.Props<T>) => {
6366
}
6467

6568
const handle_mouse_leave = () => {
69+
opened_by_shortcut.current = false
6670
set_is_open(false)
6771
set_just_opened(false)
6872
}
@@ -73,6 +77,7 @@ export const Dropdown = <T extends string>(props: Dropdown.Props<T>) => {
7377
container_ref.current &&
7478
!container_ref.current.contains(event.target as Node)
7579
) {
80+
opened_by_shortcut.current = false
7681
set_is_open(false)
7782
set_just_opened(false)
7883
}
@@ -84,6 +89,36 @@ export const Dropdown = <T extends string>(props: Dropdown.Props<T>) => {
8489
}
8590
}, [])
8691

92+
useEffect(() => {
93+
const handle_key_down = (event: KeyboardEvent) => {
94+
if (event.altKey && event.shiftKey && !is_open) {
95+
event.preventDefault()
96+
set_is_open(true)
97+
set_just_opened(true)
98+
opened_by_shortcut.current = true
99+
}
100+
}
101+
102+
const handle_key_up = (event: KeyboardEvent) => {
103+
if (
104+
opened_by_shortcut.current &&
105+
(event.key == 'Alt' || event.key == 'Shift')
106+
) {
107+
set_is_open(false)
108+
set_just_opened(false)
109+
opened_by_shortcut.current = false
110+
}
111+
}
112+
113+
document.addEventListener('keydown', handle_key_down)
114+
document.addEventListener('keyup', handle_key_up)
115+
116+
return () => {
117+
document.removeEventListener('keydown', handle_key_down)
118+
document.removeEventListener('keyup', handle_key_up)
119+
}
120+
}, [is_open])
121+
87122
return (
88123
<div
89124
className={cn(styles.container, { [styles['button--open']]: is_open })}
@@ -125,6 +160,7 @@ export const Dropdown = <T extends string>(props: Dropdown.Props<T>) => {
125160
on_click: () => handle_select(option.value),
126161
is_selected: just_opened && option.value == props.selected_value
127162
}))}
163+
underline_non_selected_items={opened_by_shortcut.current}
128164
/>
129165
)}
130166
</div>

‎packages/ui/src/components/editor/panel/PromptField/PromptField.tsx‎

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { use_handlers } from './hooks/use-handlers'
77
import { use_dropdown } from './hooks/use-dropdown'
88
import { use_drag_drop } from './hooks/use-drag-drop'
99
import { DropdownMenu } from '../../common/DropdownMenu'
10-
import { use_is_narrow_viewport } from '@shared/hooks'
10+
import { use_is_narrow_viewport, use_is_mac } from '@shared/hooks'
1111
import { search_paths } from '@shared/utils/search-paths'
1212
import { get_display_text } from './utils/get-display-text'
1313
import {
@@ -135,19 +135,8 @@ export const PromptField: React.FC<PromptFieldProps> = (props) => {
135135
[]
136136
)
137137

138-
const is_mac = useMemo(() => {
139-
if (typeof navigator == 'undefined') return false
140-
const any_nav = navigator as any
141-
const uach_platform: string | undefined = any_nav?.userAgentData?.platform
142-
if (typeof uach_platform == 'string') {
143-
return uach_platform.toLowerCase().includes('mac')
144-
}
145-
if (typeof navigator.userAgent == 'string') {
146-
return /mac/i.test(navigator.userAgent)
147-
}
148-
return false
149-
}, [])
150-
const mod_key = is_mac ? 'cmd' : 'ctrl'
138+
const is_mac = use_is_mac()
139+
const mod_key = is_mac ? '⌘' : 'Ctrl'
151140

152141
const display_text = useMemo(() => {
153142
return get_display_text(props.value, props.context_file_paths ?? [])
@@ -521,12 +510,12 @@ export const PromptField: React.FC<PromptFieldProps> = (props) => {
521510
items={[
522511
{
523512
label: 'Select...',
524-
shortcut: `${mod_key}+${is_mac ? 'return' : 'enter'}`,
513+
shortcut: `${mod_key}+${is_mac ? 'Return' : 'Enter'}`,
525514
on_click: handle_select_click
526515
},
527516
{
528517
label: 'Copy prompt',
529-
shortcut: `${mod_key}+${is_mac ? 'option' : 'alt'}+c`,
518+
shortcut: `${mod_key}+${is_mac ? 'Option' : 'Alt'}+C`,
530519
on_click: handle_copy_click
531520
}
532521
]}

‎packages/ui/src/components/editor/panel/Responses/Responses.module.scss‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
font-size: 12px !important;
6363
}
6464

65-
&__manual-entry {
65+
&__no-instructions {
6666
font-style: italic;
6767
padding-right: 1px;
6868
}

‎packages/ui/src/components/editor/panel/Responses/Responses.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,11 @@ export const Responses: React.FC<Props> = (props) => {
7373
<span
7474
className={
7575
styles[
76-
'responses__item__content__instructions__manual-entry'
76+
'responses__item__content__instructions__no-instructions'
7777
]
7878
}
7979
>
80-
Added manually
80+
Response preview
8181
</span>
8282
)}
8383
</div>

‎packages/vscode/src/constants/instructions.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export const code_completion_instructions_for_panel = (
66
row: number,
77
column: number
88
) =>
9-
`Find correct replacement text for the <missing_text> symbol. Your response must begin with a code block containing the replacement text, followed by an explanation. The first line of the code block must be a comment containing: ${file_path} ${
9+
`Find correct replacement text for the <missing_text> symbol. Your response must begin with a Markdown Code Block containing the replacement text, followed by an explanation. The first line of the Markdown Code Block must be a commented-out text: "${file_path}" ${
1010
row + 1
1111
}:${
1212
column + 1

‎packages/vscode/src/views/panel/frontend/Main/Main.tsx‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ type Props = {
4141
web_prompt_type: WebPromptType
4242
api_prompt_type: ApiPromptType
4343
on_mode_change: (mode: Mode) => void
44-
on_web_mode_change: (mode: WebPromptType) => void
45-
on_api_mode_change: (mode: ApiPromptType) => void
44+
on_web_prompt_type_change: (mode: WebPromptType) => void
45+
on_api_prompt_type_change: (mode: ApiPromptType) => void
4646
has_active_editor: boolean
4747
has_active_selection: boolean
4848
chat_input_focus_and_select_key: number
@@ -687,8 +687,8 @@ export const Main: React.FC<Props> = (props) => {
687687
web_prompt_type={props.web_prompt_type}
688688
api_prompt_type={props.api_prompt_type}
689689
context_size_warning_threshold={props.context_size_warning_threshold}
690-
on_web_mode_change={props.on_web_mode_change}
691-
on_api_mode_change={props.on_api_mode_change}
690+
on_web_prompt_type_change={props.on_web_prompt_type_change}
691+
on_api_prompt_type_change={props.on_api_prompt_type_change}
692692
chat_edit_format={chat_edit_format}
693693
api_edit_format={api_edit_format}
694694
edit_format_instructions={edit_format_instructions}

0 commit comments

Comments
 (0)