Skip to content

Commit 3281357

Browse files
committed
Introduce a reusable Dropdown component and use it to replace the native HTML select element in the General Settings section
1 parent f798f2f commit 3281357

8 files changed

Lines changed: 172 additions & 16 deletions

File tree

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
.dropdown {
2+
position: relative;
3+
width: 100%;
4+
}
5+
6+
.button {
7+
display: flex;
8+
align-items: center;
9+
justify-content: space-between;
10+
gap: var(--padding-8px);
11+
background: var(--vscode-input-background);
12+
color: var(--vscode-input-foreground);
13+
border: 1px solid var(--cwc-border-color);
14+
padding: var(--padding-4px) var(--padding-8px);
15+
border-radius: var(--border-radius-4px);
16+
cursor: pointer;
17+
width: 100%;
18+
text-align: left;
19+
20+
&:hover {
21+
background: var(--vscode-list-hoverBackground);
22+
}
23+
24+
&[data-is-open='true'] {
25+
border-color: var(--vscode-focusBorder);
26+
outline: 1px solid var(--vscode-focusBorder);
27+
outline-offset: -1px;
28+
}
29+
}
30+
31+
.chevron {
32+
transition: transform 0.2s ease;
33+
color: var(--vscode-icon-foreground);
34+
}
35+
36+
.chevron_open {
37+
transform: rotate(180deg);
38+
}
39+
40+
.menu_override {
41+
left: 0;
42+
right: auto;
43+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { useState } from 'react'
2+
import { Dropdown } from './Dropdown'
3+
4+
export default {
5+
component: Dropdown
6+
}
7+
8+
const sample_options: Dropdown.Option<string>[] = [
9+
{ value: 'option1', label: 'Option 1' },
10+
{ value: 'option2', label: 'Option 2' },
11+
{ value: 'option3', label: 'Option 3' }
12+
]
13+
14+
export const Default = () => {
15+
const [value, set_value] = useState('option1')
16+
17+
return (
18+
<div style={{ width: '220px' }}>
19+
<Dropdown options={sample_options} value={value} onChange={set_value} />
20+
</div>
21+
)
22+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { useState, useRef, useEffect } from 'react'
2+
import cn from 'classnames'
3+
import { DropdownMenu } from '../DropdownMenu'
4+
import styles from './Dropdown.module.scss'
5+
6+
export namespace Dropdown {
7+
export type Option<T extends string> = {
8+
value: T
9+
label: string
10+
}
11+
12+
export type Props<T extends string> = {
13+
options: Option<T>[]
14+
value: T
15+
onChange: (value: T) => void
16+
className?: string
17+
max_width?: number
18+
}
19+
}
20+
21+
export const Dropdown = <T extends string>({
22+
options,
23+
value,
24+
onChange,
25+
className,
26+
max_width
27+
}: Dropdown.Props<T>) => {
28+
const [is_open, set_is_open] = useState(false)
29+
const dropdown_ref = useRef<HTMLDivElement>(null)
30+
31+
const selected_option = options.find((opt) => opt.value === value)
32+
33+
const handle_click_outside = (event: MouseEvent) => {
34+
if (
35+
dropdown_ref.current &&
36+
!dropdown_ref.current.contains(event.target as Node)
37+
) {
38+
set_is_open(false)
39+
}
40+
}
41+
42+
useEffect(() => {
43+
document.addEventListener('mousedown', handle_click_outside)
44+
return () => {
45+
document.removeEventListener('mousedown', handle_click_outside)
46+
}
47+
}, [])
48+
49+
const menu_items: DropdownMenu.Item[] = options.map((option) => ({
50+
label: option.label,
51+
is_selected: option.value === value,
52+
on_click: () => {
53+
onChange(option.value)
54+
set_is_open(false)
55+
}
56+
}))
57+
58+
const style = max_width ? { maxWidth: `${max_width}px` } : undefined
59+
60+
return (
61+
<div
62+
className={cn(styles.dropdown, className)}
63+
ref={dropdown_ref}
64+
style={style}
65+
>
66+
<button
67+
className={styles.button}
68+
onClick={() => set_is_open(!is_open)}
69+
data-is-open={is_open}
70+
>
71+
<span>{selected_option?.label ?? 'Select...'}</span>
72+
<span
73+
className={cn(
74+
'codicon codicon-chevron-down',
75+
styles.chevron,
76+
is_open && styles.chevron_open
77+
)}
78+
/>
79+
</button>
80+
{is_open && (
81+
<DropdownMenu items={menu_items} className={styles.menu_override} />
82+
)}
83+
</div>
84+
)
85+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './Dropdown'

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
border-radius: var(--border-radius-8px);
77
border: 1px solid var(--vscode-widget-border);
88
box-shadow: 0 0 8px 2px var(--vscode-widget-shadow);
9-
z-index: 1;
9+
z-index: 2;
1010
padding: var(--padding-4px);
1111
width: max-content;
1212
text-align: left;

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,17 @@ export namespace DropdownMenu {
1212
export type Props = {
1313
items: Item[]
1414
on_mouse_enter?: React.MouseEventHandler<HTMLDivElement>
15+
className?: string
1516
}
1617
}
1718

1819
export const DropdownMenu: React.FC<DropdownMenu.Props> = ({
1920
items,
20-
on_mouse_enter
21+
on_mouse_enter,
22+
className
2123
}) => {
2224
return (
23-
<div className={cn(styles.menu)} onMouseEnter={on_mouse_enter}>
25+
<div className={cn(styles.menu, className)} onMouseEnter={on_mouse_enter}>
2426
{items.map((item, index) => (
2527
<div
2628
key={index}

‎packages/vscode/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "gemini-coder",
33
"displayName": "Code Web Chat",
44
"description": "The privacy AI coding without agents (CWC)",
5-
"version": "1.511.0",
5+
"version": "1.512.0",
66
"scripts": {
77
"build": "npx vsce package --no-dependencies",
88
"vscode:prepublish": "rimraf out && npm run compile",

‎packages/vscode/src/views/settings/frontend/Home/sections/GeneralSection.tsx‎

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
11
import { forwardRef, useEffect, useState } from 'react'
22
import { Input } from '@ui/components/editor/common/Input'
3+
import { Dropdown } from '@ui/components/editor/common/Dropdown'
34
import { Item } from '@ui/components/editor/settings/Item'
45
import { Section } from '@ui/components/editor/settings/Section'
56
import { TextButton } from '@ui/components/editor/settings/TextButton'
67

8+
type ClearChecksBehavior = 'ignore-open-editors' | 'uncheck-all'
9+
10+
const CLEAR_CHECKS_OPTIONS: Dropdown.Option<ClearChecksBehavior>[] = [
11+
{ value: 'ignore-open-editors', label: 'Ignore Open Editors' },
12+
{ value: 'uncheck-all', label: 'Uncheck All' }
13+
]
14+
715
type Props = {
816
context_size_warning_threshold: number
9-
clear_checks_in_workspace_behavior: 'ignore-open-editors' | 'uncheck-all'
17+
clear_checks_in_workspace_behavior: ClearChecksBehavior
1018
on_context_size_warning_threshold_change: (threshold: number) => void
1119
on_clear_checks_in_workspace_behavior_change: (
12-
value: 'ignore-open-editors' | 'uncheck-all'
20+
value: ClearChecksBehavior
1321
) => void
1422
on_open_editor_settings: () => void
1523
on_stuck_change: (is_stuck: boolean) => void
@@ -77,17 +85,12 @@ export const GeneralSection = forwardRef<HTMLDivElement, Props>(
7785
title="Clear Checks in Workspace Behavior"
7886
description="Behavior of the 'Clear Checks' command in the Workspace view."
7987
slot={
80-
<select
88+
<Dropdown
89+
options={CLEAR_CHECKS_OPTIONS}
8190
value={props.clear_checks_in_workspace_behavior}
82-
onChange={(e) =>
83-
props.on_clear_checks_in_workspace_behavior_change(
84-
e.target.value as 'ignore-open-editors' | 'uncheck-all'
85-
)
86-
}
87-
>
88-
<option value="ignore-open-editors">Ignore Open Editors</option>
89-
<option value="uncheck-all">Uncheck All</option>
90-
</select>
91+
onChange={props.on_clear_checks_in_workspace_behavior_change}
92+
max_width={320}
93+
/>
9194
}
9295
/>
9396
</Section>

0 commit comments

Comments
 (0)