From ac5cdc642b32803047aae2827ef3295263854976 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 12 Jun 2026 15:13:33 +0000 Subject: [PATCH 01/27] Voice widget: disable search by default, add requireProvider, tighter badge, hide group tabs - Search bar now defaults to hidden (opt-in via filters={{ search: true }}) - New requireProvider prop: shows placeholder until a provider is selected - Reduced badge-to-title vertical gap (margin: -0.25rem -0.15rem -0.45rem 0) - Group-by toggle hidden when provider is locked or requireProvider is set - Other filters (language, gender, pageSize) hidden while awaiting provider - TTS overview page updated with requireProvider full-catalog widget Co-Authored-By: bot_apk --- fern/components/voice-widget/index.tsx | 42 +++++++++++++------ fern/components/voice-widget/styles.css | 6 ++- .../pages/calling/voice/TTS/index.mdx | 6 +++ 3 files changed, 39 insertions(+), 15 deletions(-) diff --git a/fern/components/voice-widget/index.tsx b/fern/components/voice-widget/index.tsx index fef88d3c57..e49757514c 100644 --- a/fern/components/voice-widget/index.tsx +++ b/fern/components/voice-widget/index.tsx @@ -129,10 +129,17 @@ export interface VoiceWidgetProps { /** * Toggle the filter/search controls. `true` or unset shows all; `false` hides all (just the * title + grid). An object toggles individual controls — `search`, `provider`, `language`, - * `gender`, `group`, `pageSize` — each defaulting on unless set `false`. The provider control is + * `gender`, `group`, `pageSize` — each defaulting on unless set `false`. The search control + * defaults to `false` (hidden) unless explicitly set to `true`. The provider control is * always hidden when the `provider` lock prop is set. */ filters?: boolean | Partial>; + /** + * When `true`, the widget shows a placeholder prompting the user to select a provider before + * rendering any voice cards. The provider dropdown is always shown when this is set. Once the + * user selects a provider, cards appear. Default: `false`. + */ + requireProvider?: boolean; } export function VoiceWidget({ @@ -146,6 +153,7 @@ export function VoiceWidget({ provider: lockedProvider, voiceIds, filters, + requireProvider = false, }: VoiceWidgetProps) { // Normalized single-provider lock (null when the widget shows all providers). const lock = lockedProvider?.trim().toLowerCase() || null; @@ -186,13 +194,16 @@ export function VoiceWidget({ const pageSizeOpts = [...new Set([...PAGE_SIZE_OPTIONS, pageSize])].sort((a, b) => a - b); // Resolve which filter/search controls are visible. `filters` is a boolean (all on/off) or a - // per-control object; each control defaults on unless explicitly false. + // per-control object; each control defaults on unless explicitly false. The `search` control + // defaults to off (must be opted in) — all others default on. const showFilter = (key: FilterKey) => filters === false ? false - : filters && typeof filters === "object" ? filters[key] !== false - : true; - const showProvider = !lock && showFilter("provider"); - const showGroup = group !== "none" && showFilter("group"); + : filters && typeof filters === "object" + ? key === "search" ? filters[key] === true : filters[key] !== false + : key === "search" ? false : true; + const showProvider = (!lock || requireProvider) && showFilter("provider"); + // Hide group-by toggle when a provider is locked or requireProvider is active. + const showGroup = group !== "none" && showFilter("group") && !lock && !requireProvider; useEffect(() => { let alive = true; @@ -329,7 +340,8 @@ export function VoiceWidget({ // Step from safePage, not the raw page state: `page` can be stale-high after effectivePageSize // grows (a mobile→desktop resize isn't in filterSig), and stepping from it makes Prev appear // dead until the raw value catches back up with the clamped one. - const pager = pageCount > 1 ? ( + const awaitingProvider = requireProvider && provider === ALL; + const pager = pageCount > 1 && !awaitingProvider ? (