Skip to content

Commit 62fb0a5

Browse files
committed
Style changes
* Use the correct SVG icon * Dropdown styling changes
1 parent 210e3f7 commit 62fb0a5

File tree

8 files changed

+180
-53
lines changed

8 files changed

+180
-53
lines changed

special-pages/pages/new-tab/app/components/Icons.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -442,16 +442,17 @@ export function GlobeIcon(props) {
442442
}
443443

444444
/**
445-
* From https://dub.duckduckgo.com/duckduckgo/Icons/blob/Main/Glyphs/16px/Image-16.svg
445+
* From DesignResourcesKit Image-16.svg (matches apple-browsers AI chat omnibar)
446446
* @param {import('preact').JSX.SVGAttributes<SVGSVGElement>} props
447447
*/
448448
export function ImageIcon(props) {
449449
return (
450450
<svg width="16" height="16" fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" {...props}>
451+
<path fill="currentColor" d="M4.5 7a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z" />
451452
<path
452453
fill="currentColor"
453454
fill-rule="evenodd"
454-
d="M3 1.25A1.75 1.75 0 0 0 1.25 3v10c0 .966.784 1.75 1.75 1.75h10A1.75 1.75 0 0 0 14.75 13V3A1.75 1.75 0 0 0 13 1.25H3ZM2.5 3a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 .5.5v7.44l-1.97-1.97a1.75 1.75 0 0 0-2.474 0L7.47 10.056 6.53 9.116a1.75 1.75 0 0 0-2.474 0L2.5 10.672V3Zm0 9.44 2.44-2.44a.5.5 0 0 1 .706 0l1.147 1.147a.625.625 0 0 0 .884 0l2.38-2.38a.5.5 0 0 1 .706 0l2.737 2.737V13a.5.5 0 0 1-.5.5H3a.5.5 0 0 1-.5-.5v-.56ZM10.5 5a1 1 0 1 1 2 0 1 1 0 0 1-2 0Z"
455+
d="M4 1a4 4 0 0 0-4 4v6a4 4 0 0 0 4 4h8a4 4 0 0 0 4-4V5a4 4 0 0 0-4-4H4ZM1.25 5A2.75 2.75 0 0 1 4 2.25h8A2.75 2.75 0 0 1 14.75 5v3.866l-2.808-2.808a.625.625 0 0 0-.884 0L8 9.116 6.942 8.058a.625.625 0 0 0-.884 0l-4.394 4.394A2.737 2.737 0 0 1 1.25 11V5Zm7.634 5 1.058 1.058a.625.625 0 1 1-.884.884L6.5 9.384l-3.952 3.952A2.74 2.74 0 0 0 4 13.75h8A2.75 2.75 0 0 0 14.75 11v-.366l-3.25-3.25L8.884 10Z"
455456
clip-rule="evenodd"
456457
/>
457458
</svg>

special-pages/pages/new-tab/app/omnibar/components/AiChatForm.js

Lines changed: 71 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { h } from 'preact';
1+
import { Fragment, h } from 'preact';
22
import { useContext, useEffect, useLayoutEffect, useRef, useState } from 'preact/hooks';
33
import { eventToTarget } from '../../../../../shared/handlers';
44
import { ArrowRightIcon, ImageIcon, CloseSmallIcon, ChevronSmall } from '../../components/Icons';
@@ -73,6 +73,7 @@ export function AiChatForm({ query, autoFocus, onChange, onSubmit }) {
7373
}, [query]);
7474

7575
const disabled = query.length === 0;
76+
const imageUploadDisabled = attachedImages.length >= MAX_IMAGES;
7677

7778
/**
7879
* @param {string} chat
@@ -332,12 +333,17 @@ export function AiChatForm({ query, autoFocus, onChange, onSubmit }) {
332333
>
333334
<div class={styles.toolButtons}>
334335
<label
335-
class={styles.toolButton}
336+
class={`${styles.toolButton} ${imageUploadDisabled ? styles.toolButtonDisabled : ''}`}
336337
aria-label={t('omnibar_attachImageLabel')}
338+
aria-disabled={imageUploadDisabled}
337339
role="button"
338-
tabIndex={0}
339-
onClick={(e) => e.stopPropagation()}
340+
tabIndex={imageUploadDisabled ? -1 : 0}
341+
onClick={(e) => {
342+
e.stopPropagation();
343+
if (imageUploadDisabled) e.preventDefault();
344+
}}
340345
onKeyDown={(e) => {
346+
if (imageUploadDisabled) return;
341347
if (e.key === 'Enter' || e.key === ' ') {
342348
e.preventDefault();
343349
fileInputRef.current?.click();
@@ -350,6 +356,7 @@ export function AiChatForm({ query, autoFocus, onChange, onSubmit }) {
350356
type="file"
351357
accept="image/jpeg,image/png,image/webp"
352358
multiple
359+
disabled={imageUploadDisabled}
353360
class={styles.hiddenFileInput}
354361
onChange={handleFileChange}
355362
/>
@@ -361,7 +368,7 @@ export function AiChatForm({ query, autoFocus, onChange, onSubmit }) {
361368
<button
362369
ref={modelButtonRef}
363370
type="button"
364-
class={`${styles.modelButton} ${selectedModelId ? styles.toolButtonActive : ''}`}
371+
class={`${styles.modelButton} ${modelDropdownOpen ? styles.modelButtonOpen : ''}`}
365372
aria-label={t('omnibar_modelSelectorLabel')}
366373
aria-haspopup="listbox"
367374
aria-expanded={modelDropdownOpen}
@@ -393,29 +400,69 @@ export function AiChatForm({ query, autoFocus, onChange, onSubmit }) {
393400
</div>
394401
</div>
395402
{modelDropdownOpen && dropdownPos && (
396-
<ul
397-
class={styles.modelDropdown}
398-
role="listbox"
399-
aria-label={t('omnibar_modelSelectorLabel')}
400-
style={{ right: `${dropdownPos.right}px`, top: `${dropdownPos.top}px` }}
403+
<ModelDropdown
404+
models={aiModels}
405+
selectedModelId={selectedModelId ?? aiModels[0]?.id}
406+
dropdownPos={dropdownPos}
407+
onSelect={(id) => {
408+
setSelectedModelId(id);
409+
setModelDropdownOpen(false);
410+
}}
411+
ariaLabel={t('omnibar_modelSelectorLabel')}
412+
sectionHeader={t('omnibar_advancedModelsSectionHeader')}
413+
/>
414+
)}
415+
</form>
416+
);
417+
}
418+
419+
/**
420+
* @param {object} props
421+
* @param {import('../../../types/new-tab.js').AIModels} props.models
422+
* @param {string} [props.selectedModelId]
423+
* @param {{right: number, top: number}} props.dropdownPos
424+
* @param {(id: string) => void} props.onSelect
425+
* @param {string} props.ariaLabel
426+
* @param {string} props.sectionHeader
427+
*/
428+
function ModelDropdown({ models, selectedModelId, dropdownPos, onSelect, ariaLabel, sectionHeader }) {
429+
const freeModels = models.filter((m) => m.entityHasAccess !== false);
430+
const premiumModels = models.filter((m) => m.entityHasAccess === false);
431+
432+
return (
433+
<ul
434+
class={styles.modelDropdown}
435+
role="listbox"
436+
aria-label={ariaLabel}
437+
style={{ right: `${dropdownPos.right}px`, top: `${dropdownPos.top}px` }}
438+
>
439+
{freeModels.map((model) => (
440+
<li
441+
key={model.id}
442+
role="option"
443+
aria-selected={model.id === selectedModelId}
444+
class={`${styles.modelOption} ${model.id === selectedModelId ? styles.modelOptionSelected : ''}`}
445+
onClick={(e) => {
446+
e.stopPropagation();
447+
onSelect(model.id);
448+
}}
401449
>
402-
{aiModels.map((model) => (
403-
<li
404-
key={model.id}
405-
role="option"
406-
aria-selected={model.id === (selectedModelId ?? aiModels[0]?.id)}
407-
class={`${styles.modelOption} ${model.id === (selectedModelId ?? aiModels[0]?.id) ? styles.modelOptionSelected : ''}`}
408-
onClick={(e) => {
409-
e.stopPropagation();
410-
setSelectedModelId(model.id);
411-
setModelDropdownOpen(false);
412-
}}
413-
>
450+
{model.name}
451+
</li>
452+
))}
453+
{premiumModels.length > 0 && (
454+
<Fragment>
455+
<li role="separator" class={styles.modelSectionDivider} />
456+
<li role="presentation" class={styles.modelSectionHeader}>
457+
{sectionHeader}
458+
</li>
459+
{premiumModels.map((model) => (
460+
<li key={model.id} role="option" aria-disabled="true" class={`${styles.modelOption} ${styles.modelOptionDisabled}`}>
414461
{model.name}
415462
</li>
416463
))}
417-
</ul>
464+
</Fragment>
418465
)}
419-
</form>
466+
</ul>
420467
);
421468
}

special-pages/pages/new-tab/app/omnibar/components/AiChatForm.module.css

Lines changed: 82 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,11 @@
4848
}
4949

5050
.thumbnail {
51-
border-radius: 6px;
52-
height: 40px;
51+
border-radius: 8px;
52+
border: 1px solid var(--ds-color-theme-surface-decoration-tertiary);
53+
height: 48px;
5354
object-fit: cover;
54-
width: 40px;
55+
width: 48px;
5556
}
5657

5758
.thumbnailWrapper {
@@ -60,22 +61,22 @@
6061

6162
.thumbnailRemove {
6263
align-items: center;
63-
background: var(--ds-color-theme-surface-primary);
64+
background: var(--ds-color-theme-text-primary);
6465
border-radius: 100%;
65-
border: 1px solid var(--ds-color-theme-surface-decoration-tertiary);
66-
color: var(--ds-color-theme-icons-primary);
66+
border: none;
67+
color: var(--ds-color-theme-surface-primary);
6768
cursor: pointer;
6869
display: flex;
69-
height: 16px;
70+
height: 18px;
7071
justify-content: center;
7172
padding: 0;
7273
position: absolute;
73-
right: -4px;
74-
top: -4px;
75-
width: 16px;
74+
right: -5px;
75+
top: -5px;
76+
width: 18px;
7677

7778
&:hover {
78-
background: var(--ds-color-theme-surface-secondary);
79+
opacity: 0.8;
7980
}
8081

8182
&:focus-visible {
@@ -132,6 +133,12 @@
132133
}
133134
}
134135

136+
.toolButtonDisabled {
137+
color: color-mix(in srgb, var(--ds-color-theme-icons-primary) 30%, transparent);
138+
cursor: default;
139+
pointer-events: none;
140+
}
141+
135142
.toolButtonActive {
136143
background: var(--ds-color-theme-surface-secondary);
137144
color: var(--ds-color-theme-accent-primary);
@@ -177,9 +184,9 @@
177184

178185
.modelButton {
179186
align-items: center;
180-
background: var(--ds-color-theme-surface-secondary);
187+
background: none;
181188
border-radius: 100px;
182-
border: 1px solid var(--ds-color-theme-surface-decoration-tertiary);
189+
border: 1px solid transparent;
183190
color: var(--ds-color-theme-text-secondary);
184191
cursor: pointer;
185192
display: flex;
@@ -189,18 +196,34 @@
189196
padding: 0 var(--sp-1) 0 var(--sp-2);
190197

191198
&:hover {
192-
background: var(--ds-color-theme-surface-tertiary);
199+
background: rgba(0, 0, 0, 0.05);
200+
border-color: var(--ds-color-theme-surface-decoration-tertiary);
193201
}
194202

195203
&:active {
196-
background: var(--ds-color-theme-surface-tertiary);
204+
background: rgba(0, 0, 0, 0.18);
197205
}
198206

199207
&:focus-visible {
200208
box-shadow: var(--focus-ring);
201209
}
202210
}
203211

212+
.modelButtonOpen {
213+
background: rgba(0, 0, 0, 0.55);
214+
border-color: transparent;
215+
color: #fff;
216+
217+
&:hover {
218+
background: rgba(0, 0, 0, 0.58);
219+
border-color: transparent;
220+
}
221+
222+
&:active {
223+
background: rgba(0, 0, 0, 0.62);
224+
}
225+
}
226+
204227
.modelButtonLabel {
205228
max-width: 120px;
206229
overflow: hidden;
@@ -210,33 +233,69 @@
210233

211234
.modelDropdown {
212235
background: var(--ds-color-theme-surface-primary);
213-
border-radius: 8px;
236+
border-radius: 16px;
214237
border: 1px solid var(--ds-color-theme-surface-decoration-tertiary);
215-
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
238+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
216239
list-style: none;
217240
margin: 0;
218-
max-height: 240px;
219-
min-width: 180px;
241+
max-height: 400px;
242+
min-width: 280px;
220243
overflow-y: auto;
221-
padding: var(--sp-1);
244+
padding: var(--sp-2);
222245
position: fixed;
223246
z-index: 100;
224247
}
225248

226249
.modelOption {
227-
border-radius: 6px;
250+
align-items: center;
228251
cursor: pointer;
229-
font-size: 12px;
252+
display: flex;
253+
font-size: 14px;
254+
gap: var(--sp-2);
230255
padding: var(--sp-2) var(--sp-3);
231256

257+
&::before {
258+
color: transparent;
259+
content: '\2713';
260+
flex-shrink: 0;
261+
font-size: 14px;
262+
font-weight: 600;
263+
width: 16px;
264+
}
265+
232266
&:hover {
233267
background: var(--ds-color-theme-surface-secondary);
268+
border-radius: 8px;
234269
}
235270
}
236271

237272
.modelOptionSelected {
238-
color: var(--ds-color-theme-accent-primary);
239273
font-weight: 600;
274+
275+
&::before {
276+
color: var(--ds-color-theme-text-primary);
277+
}
278+
}
279+
280+
.modelOptionDisabled {
281+
color: var(--ds-color-theme-text-tertiary);
282+
cursor: default;
283+
284+
&:hover {
285+
background: none;
286+
}
287+
}
288+
289+
.modelSectionDivider {
290+
border-top: 1px solid var(--ds-color-theme-surface-decoration-tertiary);
291+
margin: var(--sp-1) var(--sp-3);
292+
}
293+
294+
.modelSectionHeader {
295+
color: var(--ds-color-theme-text-tertiary);
296+
cursor: default;
297+
font-size: 12px;
298+
padding: var(--sp-2) var(--sp-3);
240299
}
241300

242301
.hiddenFileInput {

special-pages/pages/new-tab/app/omnibar/mocks/omnibar.mock-transport.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,13 @@ export function omnibarMockTransport() {
2424
showCustomizePopover: false,
2525
enableRecentAiChats: false,
2626
aiModels: [
27-
{ id: 'gpt-4o-mini', name: 'GPT-4o mini' },
28-
{ id: 'claude-haiku-4-5', name: 'Claude Haiku 4.5' },
29-
{ id: 'meta-llama/Llama-4-Scout-17B-16E-Instruct', name: 'Llama 4 Scout' },
30-
{ id: 'mistralai/Mistral-Small-24B-Instruct-2501', name: 'Mistral Small 3' },
27+
{ id: 'gpt-4o-mini', name: 'GPT-4o mini', entityHasAccess: true },
28+
{ id: 'claude-haiku-4-5', name: 'Claude Haiku 4.5', entityHasAccess: true },
29+
{ id: 'meta-llama/Llama-4-Scout-17B-16E-Instruct', name: 'Llama 4 Scout', entityHasAccess: true },
30+
{ id: 'mistralai/Mistral-Small-24B-Instruct-2501', name: 'Mistral Small 3', entityHasAccess: true },
31+
{ id: 'gpt-4o', name: 'GPT-4o', entityHasAccess: false },
32+
{ id: 'claude-sonnet-4-5', name: 'Claude Sonnet 4.5', entityHasAccess: false },
33+
{ id: 'meta-llama/Llama-4-Maverick', name: 'Llama 4 Maverick', entityHasAccess: false },
3134
],
3235
};
3336

special-pages/pages/new-tab/app/omnibar/strings.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,5 +78,9 @@
7878
"omnibar_modelSelectorLabel": {
7979
"title": "Select model",
8080
"description": "Accessible label for the AI model selector dropdown."
81+
},
82+
"omnibar_advancedModelsSectionHeader": {
83+
"title": "Advanced Models - DuckDuckGo subscription",
84+
"description": "Section header in the model picker for premium models that require a subscription."
8185
}
8286
}

special-pages/pages/new-tab/messages/types/omnibar-config.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@
4747
"name": {
4848
"description": "Display name",
4949
"type": "string"
50+
},
51+
"entityHasAccess": {
52+
"description": "Whether the user has access to this model (false = requires subscription)",
53+
"type": "boolean",
54+
"default": true
5055
}
5156
}
5257
}

special-pages/pages/new-tab/public/locales/en/new-tab.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,10 @@
241241
"title": "Select model",
242242
"description": "Accessible label for the AI model selector dropdown."
243243
},
244+
"omnibar_advancedModelsSectionHeader": {
245+
"title": "Advanced Models - DuckDuckGo subscription",
246+
"description": "Section header in the model picker for premium models that require a subscription."
247+
},
244248
"nextStepsList_sectionTitle": {
245249
"title": "Next Steps",
246250
"note": "Text that goes in the Next Steps bubble label above the card"

0 commit comments

Comments
 (0)