diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index ca7cc0325..dcd5bd2f1 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -1,5 +1,5 @@ import { useFilteredList } from "@opencode-ai/ui/hooks" -import { createEffect, on, Component, Show, For, onCleanup, Switch, Match, createMemo, createSignal } from "solid-js" +import { createEffect, on, Component, Show, onCleanup, Switch, Match, createMemo, createSignal } from "solid-js" import { createStore } from "solid-js/store" import { createFocusSignal } from "@solid-primitives/active-element" import { useLocal } from "@/context/local" @@ -26,6 +26,7 @@ import type { IconName } from "@opencode-ai/ui/icons/provider" import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip" import { IconButton } from "@opencode-ai/ui/icon-button" import { Select } from "@opencode-ai/ui/select" +import { RadioGroup } from "@opencode-ai/ui/radio-group" import { useDialog } from "@opencode-ai/ui/context/dialog" import { ModelSelectorPopover } from "@/components/dialog-select-model" import { DialogSelectModelUnpaid } from "@/components/dialog-select-model-unpaid" @@ -249,7 +250,6 @@ export const PromptInput: Component = (props) => { return messages.some((m) => m.role === "user") }) - const MAX_HISTORY = 100 const [history, setHistory] = persisted( Persist.global("prompt-history", ["prompt-history.v1"]), createStore<{ @@ -319,6 +319,9 @@ export const PromptInput: Component = (props) => { requestAnimationFrame(() => editorRef?.focus()) } + const shellModeKey = "mod+shift+x" + const normalModeKey = "mod+shift+e" + command.register("prompt-input", () => [ { id: "file.attach", @@ -328,6 +331,22 @@ export const PromptInput: Component = (props) => { disabled: store.mode !== "normal", onSelect: pick, }, + { + id: "prompt.mode.shell", + title: language.t("command.prompt.mode.shell"), + category: language.t("command.category.session"), + keybind: shellModeKey, + disabled: store.mode === "shell", + onSelect: () => setMode("shell"), + }, + { + id: "prompt.mode.normal", + title: language.t("command.prompt.mode.normal"), + category: language.t("command.category.session"), + keybind: normalModeKey, + disabled: store.mode === "normal", + onSelect: () => setMode("normal"), + }, ]) const closePopover = () => setStore("popover", null) @@ -1339,45 +1358,35 @@ export const PromptInput: Component = (props) => { - -
-
-
- - -
+ + + )} + onSelect={(mode) => mode && setMode(mode)} + fill + pad="none" + class="w-[68px]" + />
diff --git a/packages/app/src/i18n/ar.ts b/packages/app/src/i18n/ar.ts index 1116d88d1..470b54a6a 100644 --- a/packages/app/src/i18n/ar.ts +++ b/packages/app/src/i18n/ar.ts @@ -63,6 +63,8 @@ export const dict = { "command.agent.cycle.reverse.description": "التبديل إلى الوكيل السابق", "command.model.variant.cycle": "تغيير جهد التفكير", "command.model.variant.cycle.description": "التبديل إلى مستوى الجهد التالي", + "command.prompt.mode.shell": "التبديل إلى وضع Shell", + "command.prompt.mode.normal": "التبديل إلى وضع Prompt", "command.permissions.autoaccept.enable": "قبول التعديلات تلقائيًا", "command.permissions.autoaccept.disable": "إيقاف قبول التعديلات تلقائيًا", "command.workspace.toggle": "تبديل مساحات العمل", @@ -210,6 +212,7 @@ export const dict = { "prompt.placeholder.summarizeComments": "لخّص التعليقات…", "prompt.placeholder.summarizeComment": "لخّص التعليق…", "prompt.mode.shell": "Shell", + "prompt.mode.normal": "Prompt", "prompt.mode.shell.exit": "esc للخروج", "prompt.example.1": "إصلاح TODO في قاعدة التعليمات البرمجية", "prompt.example.2": "ما هو المكدس التقني لهذا المشروع؟", diff --git a/packages/app/src/i18n/br.ts b/packages/app/src/i18n/br.ts index 19fac5ec4..53b456ad4 100644 --- a/packages/app/src/i18n/br.ts +++ b/packages/app/src/i18n/br.ts @@ -63,6 +63,8 @@ export const dict = { "command.agent.cycle.reverse.description": "Mudar para o agente anterior", "command.model.variant.cycle": "Alternar nível de raciocínio", "command.model.variant.cycle.description": "Mudar para o próximo nível de esforço", + "command.prompt.mode.shell": "Alternar para o modo Shell", + "command.prompt.mode.normal": "Alternar para o modo Prompt", "command.permissions.autoaccept.enable": "Aceitar edições automaticamente", "command.permissions.autoaccept.disable": "Parar de aceitar edições automaticamente", "command.workspace.toggle": "Alternar espaços de trabalho", @@ -210,6 +212,7 @@ export const dict = { "prompt.placeholder.summarizeComments": "Resumir comentários…", "prompt.placeholder.summarizeComment": "Resumir comentário…", "prompt.mode.shell": "Shell", + "prompt.mode.normal": "Prompt", "prompt.mode.shell.exit": "esc para sair", "prompt.example.1": "Corrigir um TODO no código", "prompt.example.2": "Qual é a stack tecnológica deste projeto?", diff --git a/packages/app/src/i18n/bs.ts b/packages/app/src/i18n/bs.ts index cecdb5918..065372c40 100644 --- a/packages/app/src/i18n/bs.ts +++ b/packages/app/src/i18n/bs.ts @@ -69,6 +69,8 @@ export const dict = { "command.agent.cycle.reverse.description": "Prebaci na prethodnog agenta", "command.model.variant.cycle": "Promijeni nivo razmišljanja", "command.model.variant.cycle.description": "Prebaci na sljedeći nivo", + "command.prompt.mode.shell": "Prebaci na Shell način", + "command.prompt.mode.normal": "Prebaci na Prompt način", "command.permissions.autoaccept.enable": "Automatski prihvataj izmjene", "command.permissions.autoaccept.disable": "Zaustavi automatsko prihvatanje izmjena", "command.workspace.toggle": "Prikaži/sakrij radne prostore", @@ -228,6 +230,7 @@ export const dict = { "prompt.placeholder.summarizeComments": "Sažmi komentare…", "prompt.placeholder.summarizeComment": "Sažmi komentar…", "prompt.mode.shell": "Shell", + "prompt.mode.normal": "Prompt", "prompt.mode.shell.exit": "esc za izlaz", "prompt.example.1": "Popravi TODO u bazi koda", diff --git a/packages/app/src/i18n/da.ts b/packages/app/src/i18n/da.ts index fe62d9219..886548c84 100644 --- a/packages/app/src/i18n/da.ts +++ b/packages/app/src/i18n/da.ts @@ -69,6 +69,8 @@ export const dict = { "command.agent.cycle.reverse.description": "Skift til forrige agent", "command.model.variant.cycle": "Skift tænkeindsats", "command.model.variant.cycle.description": "Skift til næste indsatsniveau", + "command.prompt.mode.shell": "Skift til shell-tilstand", + "command.prompt.mode.normal": "Skift til prompt-tilstand", "command.permissions.autoaccept.enable": "Accepter ændringer automatisk", "command.permissions.autoaccept.disable": "Stop automatisk accept af ændringer", "command.workspace.toggle": "Skift arbejdsområder", @@ -226,6 +228,7 @@ export const dict = { "prompt.placeholder.summarizeComments": "Opsummér kommentarer…", "prompt.placeholder.summarizeComment": "Opsummér kommentar…", "prompt.mode.shell": "Shell", + "prompt.mode.normal": "Prompt", "prompt.mode.shell.exit": "esc for at afslutte", "prompt.example.1": "Ret en TODO i koden", diff --git a/packages/app/src/i18n/de.ts b/packages/app/src/i18n/de.ts index d82cd305b..6e83811ef 100644 --- a/packages/app/src/i18n/de.ts +++ b/packages/app/src/i18n/de.ts @@ -67,6 +67,8 @@ export const dict = { "command.agent.cycle.reverse.description": "Zum vorherigen Agenten wechseln", "command.model.variant.cycle": "Denkaufwand wechseln", "command.model.variant.cycle.description": "Zum nächsten Aufwandslevel wechseln", + "command.prompt.mode.shell": "In den Shell-Modus wechseln", + "command.prompt.mode.normal": "In den Prompt-Modus wechseln", "command.permissions.autoaccept.enable": "Änderungen automatisch akzeptieren", "command.permissions.autoaccept.disable": "Automatische Annahme von Änderungen stoppen", "command.workspace.toggle": "Arbeitsbereiche umschalten", @@ -215,6 +217,7 @@ export const dict = { "prompt.placeholder.summarizeComments": "Kommentare zusammenfassen…", "prompt.placeholder.summarizeComment": "Kommentar zusammenfassen…", "prompt.mode.shell": "Shell", + "prompt.mode.normal": "Prompt", "prompt.mode.shell.exit": "esc zum Verlassen", "prompt.example.1": "Ein TODO in der Codebasis beheben", "prompt.example.2": "Was ist der Tech-Stack dieses Projekts?", diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index 9a4e458ad..6fcec384f 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -69,6 +69,8 @@ export const dict = { "command.agent.cycle.reverse.description": "Switch to the previous agent", "command.model.variant.cycle": "Cycle thinking effort", "command.model.variant.cycle.description": "Switch to the next effort level", + "command.prompt.mode.shell": "Switch to shell mode", + "command.prompt.mode.normal": "Switch to prompt mode", "command.permissions.autoaccept.enable": "Auto-accept edits", "command.permissions.autoaccept.disable": "Stop auto-accepting edits", "command.workspace.toggle": "Toggle workspaces", @@ -228,6 +230,7 @@ export const dict = { "prompt.placeholder.summarizeComments": "Summarize comments…", "prompt.placeholder.summarizeComment": "Summarize comment…", "prompt.mode.shell": "Shell", + "prompt.mode.normal": "Prompt", "prompt.mode.shell.exit": "esc to exit", "prompt.example.1": "Fix a TODO in the codebase", diff --git a/packages/app/src/i18n/es.ts b/packages/app/src/i18n/es.ts index a813dd450..05fc81709 100644 --- a/packages/app/src/i18n/es.ts +++ b/packages/app/src/i18n/es.ts @@ -69,6 +69,8 @@ export const dict = { "command.agent.cycle.reverse.description": "Cambiar al agente anterior", "command.model.variant.cycle": "Alternar esfuerzo de pensamiento", "command.model.variant.cycle.description": "Cambiar al siguiente nivel de esfuerzo", + "command.prompt.mode.shell": "Cambiar al modo Shell", + "command.prompt.mode.normal": "Cambiar al modo Prompt", "command.permissions.autoaccept.enable": "Aceptar ediciones automáticamente", "command.permissions.autoaccept.disable": "Dejar de aceptar ediciones automáticamente", "command.workspace.toggle": "Alternar espacios de trabajo", @@ -227,6 +229,7 @@ export const dict = { "prompt.placeholder.summarizeComments": "Resumir comentarios…", "prompt.placeholder.summarizeComment": "Resumir comentario…", "prompt.mode.shell": "Shell", + "prompt.mode.normal": "Prompt", "prompt.mode.shell.exit": "esc para salir", "prompt.example.1": "Arreglar un TODO en el código", diff --git a/packages/app/src/i18n/fr.ts b/packages/app/src/i18n/fr.ts index a817e93c7..811bb1968 100644 --- a/packages/app/src/i18n/fr.ts +++ b/packages/app/src/i18n/fr.ts @@ -63,6 +63,8 @@ export const dict = { "command.agent.cycle.reverse.description": "Passer à l'agent précédent", "command.model.variant.cycle": "Changer l'effort de réflexion", "command.model.variant.cycle.description": "Passer au niveau d'effort suivant", + "command.prompt.mode.shell": "Passer en mode Shell", + "command.prompt.mode.normal": "Passer en mode Prompt", "command.permissions.autoaccept.enable": "Accepter automatiquement les modifications", "command.permissions.autoaccept.disable": "Arrêter l'acceptation automatique des modifications", "command.workspace.toggle": "Basculer les espaces de travail", @@ -210,6 +212,7 @@ export const dict = { "prompt.placeholder.summarizeComments": "Résumer les commentaires…", "prompt.placeholder.summarizeComment": "Résumer le commentaire…", "prompt.mode.shell": "Shell", + "prompt.mode.normal": "Prompt", "prompt.mode.shell.exit": "esc pour quitter", "prompt.example.1": "Corriger un TODO dans la base de code", "prompt.example.2": "Quelle est la pile technique de ce projet ?", diff --git a/packages/app/src/i18n/ja.ts b/packages/app/src/i18n/ja.ts index d6acd7c22..6bc25c250 100644 --- a/packages/app/src/i18n/ja.ts +++ b/packages/app/src/i18n/ja.ts @@ -63,6 +63,8 @@ export const dict = { "command.agent.cycle.reverse.description": "前のエージェントに切り替え", "command.model.variant.cycle": "思考レベルの切り替え", "command.model.variant.cycle.description": "次の思考レベルに切り替え", + "command.prompt.mode.shell": "シェルモードに切り替える", + "command.prompt.mode.normal": "プロンプトモードに切り替える", "command.permissions.autoaccept.enable": "編集を自動承認", "command.permissions.autoaccept.disable": "編集の自動承認を停止", "command.workspace.toggle": "ワークスペースを切り替え", @@ -209,6 +211,7 @@ export const dict = { "prompt.placeholder.summarizeComments": "コメントを要約…", "prompt.placeholder.summarizeComment": "コメントを要約…", "prompt.mode.shell": "シェル", + "prompt.mode.normal": "プロンプト", "prompt.mode.shell.exit": "escで終了", "prompt.example.1": "コードベースのTODOを修正", "prompt.example.2": "このプロジェクトの技術スタックは何ですか?", diff --git a/packages/app/src/i18n/ko.ts b/packages/app/src/i18n/ko.ts index b8cfe6379..c3a197941 100644 --- a/packages/app/src/i18n/ko.ts +++ b/packages/app/src/i18n/ko.ts @@ -67,6 +67,8 @@ export const dict = { "command.agent.cycle.reverse.description": "이전 에이전트로 전환", "command.model.variant.cycle": "생각 수준 순환", "command.model.variant.cycle.description": "다음 생각 수준으로 전환", + "command.prompt.mode.shell": "셸 모드로 전환", + "command.prompt.mode.normal": "프롬프트 모드로 전환", "command.permissions.autoaccept.enable": "편집 자동 수락", "command.permissions.autoaccept.disable": "편집 자동 수락 중지", "command.workspace.toggle": "작업 공간 전환", @@ -213,6 +215,7 @@ export const dict = { "prompt.placeholder.summarizeComments": "댓글 요약…", "prompt.placeholder.summarizeComment": "댓글 요약…", "prompt.mode.shell": "셸", + "prompt.mode.normal": "프롬프트", "prompt.mode.shell.exit": "종료하려면 esc", "prompt.example.1": "코드베이스의 TODO 수정", "prompt.example.2": "이 프로젝트의 기술 스택이 무엇인가요?", diff --git a/packages/app/src/i18n/no.ts b/packages/app/src/i18n/no.ts index 3ecfcd444..9a193cba0 100644 --- a/packages/app/src/i18n/no.ts +++ b/packages/app/src/i18n/no.ts @@ -72,6 +72,8 @@ export const dict = { "command.agent.cycle.reverse.description": "Bytt til forrige agent", "command.model.variant.cycle": "Bytt tenkeinnsats", "command.model.variant.cycle.description": "Bytt til neste innsatsnivå", + "command.prompt.mode.shell": "Bytt til Shell-modus", + "command.prompt.mode.normal": "Bytt til Prompt-modus", "command.permissions.autoaccept.enable": "Godta endringer automatisk", "command.permissions.autoaccept.disable": "Slutt å godta endringer automatisk", "command.workspace.toggle": "Veksle arbeidsområder", @@ -230,6 +232,7 @@ export const dict = { "prompt.placeholder.summarizeComments": "Oppsummer kommentarer…", "prompt.placeholder.summarizeComment": "Oppsummer kommentar…", "prompt.mode.shell": "Shell", + "prompt.mode.normal": "Prompt", "prompt.mode.shell.exit": "ESC for å avslutte", "prompt.example.1": "Fiks en TODO i kodebasen", diff --git a/packages/app/src/i18n/pl.ts b/packages/app/src/i18n/pl.ts index 30698a957..70b7ae209 100644 --- a/packages/app/src/i18n/pl.ts +++ b/packages/app/src/i18n/pl.ts @@ -63,6 +63,8 @@ export const dict = { "command.agent.cycle.reverse.description": "Przełącz na poprzedniego agenta", "command.model.variant.cycle": "Przełącz wysiłek myślowy", "command.model.variant.cycle.description": "Przełącz na następny poziom wysiłku", + "command.prompt.mode.shell": "Przełącz na tryb terminala", + "command.prompt.mode.normal": "Przełącz na tryb Prompt", "command.permissions.autoaccept.enable": "Automatyczne akceptowanie edycji", "command.permissions.autoaccept.disable": "Zatrzymaj automatyczne akceptowanie edycji", "command.workspace.toggle": "Przełącz przestrzenie robocze", @@ -211,6 +213,7 @@ export const dict = { "prompt.placeholder.summarizeComments": "Podsumuj komentarze…", "prompt.placeholder.summarizeComment": "Podsumuj komentarz…", "prompt.mode.shell": "Terminal", + "prompt.mode.normal": "Prompt", "prompt.mode.shell.exit": "esc aby wyjść", "prompt.example.1": "Napraw TODO w bazie kodu", "prompt.example.2": "Jaki jest stos technologiczny tego projektu?", diff --git a/packages/app/src/i18n/ru.ts b/packages/app/src/i18n/ru.ts index f5cdf4c41..1b1a12b0a 100644 --- a/packages/app/src/i18n/ru.ts +++ b/packages/app/src/i18n/ru.ts @@ -69,6 +69,8 @@ export const dict = { "command.agent.cycle.reverse.description": "Переключиться к предыдущему агенту", "command.model.variant.cycle": "Цикл режимов мышления", "command.model.variant.cycle.description": "Переключиться к следующему уровню усилий", + "command.prompt.mode.shell": "Переключиться в режим оболочки", + "command.prompt.mode.normal": "Переключиться в режим промпта", "command.permissions.autoaccept.enable": "Авто-принятие изменений", "command.permissions.autoaccept.disable": "Прекратить авто-принятие изменений", "command.workspace.toggle": "Переключить рабочие пространства", @@ -227,6 +229,7 @@ export const dict = { "prompt.placeholder.summarizeComments": "Суммировать комментарии…", "prompt.placeholder.summarizeComment": "Суммировать комментарий…", "prompt.mode.shell": "Оболочка", + "prompt.mode.normal": "Промпт", "prompt.mode.shell.exit": "esc для выхода", "prompt.example.1": "Исправить TODO в коде", diff --git a/packages/app/src/i18n/th.ts b/packages/app/src/i18n/th.ts index 9a581c5b7..fbd237c08 100644 --- a/packages/app/src/i18n/th.ts +++ b/packages/app/src/i18n/th.ts @@ -69,6 +69,8 @@ export const dict = { "command.agent.cycle.reverse.description": "สลับไปยังเอเจนต์ก่อนหน้า", "command.model.variant.cycle": "เปลี่ยนความพยายามในการคิด", "command.model.variant.cycle.description": "สลับไปยังระดับความพยายามถัดไป", + "command.prompt.mode.shell": "สลับไปยังโหมดเชลล์", + "command.prompt.mode.normal": "สลับไปยังโหมดพรอมต์", "command.permissions.autoaccept.enable": "ยอมรับการแก้ไขโดยอัตโนมัติ", "command.permissions.autoaccept.disable": "หยุดยอมรับการแก้ไขโดยอัตโนมัติ", "command.workspace.toggle": "สลับพื้นที่ทำงาน", @@ -227,6 +229,7 @@ export const dict = { "prompt.placeholder.summarizeComments": "สรุปความคิดเห็น…", "prompt.placeholder.summarizeComment": "สรุปความคิดเห็น…", "prompt.mode.shell": "เชลล์", + "prompt.mode.normal": "พรอมต์", "prompt.mode.shell.exit": "กด esc เพื่อออก", "prompt.example.1": "แก้ไข TODO ในโค้ดเบส", diff --git a/packages/app/src/i18n/zh.ts b/packages/app/src/i18n/zh.ts index ed6849c7d..281cbaac1 100644 --- a/packages/app/src/i18n/zh.ts +++ b/packages/app/src/i18n/zh.ts @@ -93,6 +93,9 @@ export const dict = { "command.model.variant.cycle": "切换思考强度", "command.model.variant.cycle.description": "切换到下一个强度等级", + "command.prompt.mode.shell": "切换到 Shell 模式", + "command.prompt.mode.normal": "切换到 Prompt 模式", + "command.permissions.autoaccept.enable": "自动接受编辑", "command.permissions.autoaccept.disable": "停止自动接受编辑", @@ -248,6 +251,7 @@ export const dict = { "prompt.placeholder.summarizeComments": "总结评论…", "prompt.placeholder.summarizeComment": "总结该评论…", "prompt.mode.shell": "Shell", + "prompt.mode.normal": "Prompt", "prompt.mode.shell.exit": "按 esc 退出", "prompt.example.1": "修复代码库中的一个 TODO", "prompt.example.2": "这个项目的技术栈是什么?", diff --git a/packages/app/src/i18n/zht.ts b/packages/app/src/i18n/zht.ts index 21aafea2c..5278756a2 100644 --- a/packages/app/src/i18n/zht.ts +++ b/packages/app/src/i18n/zht.ts @@ -73,6 +73,8 @@ export const dict = { "command.agent.cycle.reverse.description": "切換到上一個代理程式", "command.model.variant.cycle": "循環思考強度", "command.model.variant.cycle.description": "切換到下一個強度等級", + "command.prompt.mode.shell": "切換到 Shell 模式", + "command.prompt.mode.normal": "切換到 Prompt 模式", "command.permissions.autoaccept.enable": "自動接受編輯", "command.permissions.autoaccept.disable": "停止自動接受編輯", "command.workspace.toggle": "切換工作區", @@ -227,6 +229,7 @@ export const dict = { "prompt.placeholder.summarizeComments": "摘要評論…", "prompt.placeholder.summarizeComment": "摘要這則評論…", "prompt.mode.shell": "Shell", + "prompt.mode.normal": "Prompt", "prompt.mode.shell.exit": "按 esc 退出", "prompt.example.1": "修復程式碼庫中的一個 TODO", diff --git a/packages/ui/src/components/radio-group.css b/packages/ui/src/components/radio-group.css index 3d672bb30..9f7bd5a9e 100644 --- a/packages/ui/src/components/radio-group.css +++ b/packages/ui/src/components/radio-group.css @@ -1,85 +1,121 @@ [data-component="radio-group"] { - display: flex; - flex-direction: column; - gap: calc(var(--spacing) * 2); + --radio-group-height: 28px; + --radio-group-gap: 4px; + --radio-group-padding: 2px; + + display: inline-flex; [data-slot="radio-group-wrapper"] { all: unset; - background-color: var(--surface-base); - border-radius: var(--radius-md); - box-shadow: var(--shadow-xs-border); + background-color: var(--surface-inset-base); + border-radius: var(--radius-sm); + box-shadow: var(--shadow-xxs-border); + display: inline-flex; + height: var(--radio-group-height); margin: 0; + overflow: visible; padding: 0; position: relative; width: fit-content; } + &[data-fill] [data-slot="radio-group-wrapper"] { + width: 100%; + } + [data-slot="radio-group-items"] { display: inline-flex; - list-style: none; flex-direction: row; + gap: var(--radio-group-gap); + height: 100%; + list-style: none; + position: relative; + z-index: 1; + } + + &[data-fill] [data-slot="radio-group-items"] { + width: 100%; } [data-slot="radio-group-indicator"] { - background: var(--button-secondary-base); - border-radius: var(--radius-md); + background: var(--surface-raised-stronger-non-alpha); + border-radius: var(--radius-sm); box-shadow: var(--shadow-xs-border); content: ""; opacity: var(--indicator-opacity, 1); + pointer-events: none; position: absolute; transition: - opacity 300ms ease-in-out, + opacity 200ms ease-out, box-shadow 100ms ease-in-out, - width 150ms ease, - height 150ms ease, - transform 150ms ease; + width 200ms ease-out, + height 200ms ease-out, + transform 200ms ease-out; + will-change: transform; + z-index: 0; } [data-slot="radio-group-item"] { + display: flex; + height: 100%; + min-width: 0; position: relative; } - /* Separator between items */ - [data-slot="radio-group-item"]:not(:first-of-type)::before { - background: var(--border-weak-base); - border-radius: var(--radius-xs); - content: ""; - inset: 6px 0; - position: absolute; - transition: opacity 150ms ease; - width: 1px; - transform: translateX(-0.5px); + &[data-fill] [data-slot="radio-group-item"] { + flex: 1; } - /* Hide separator when item or previous item is checked */ - [data-slot="radio-group-item"]:has([data-slot="radio-group-item-input"][data-checked])::before, - [data-slot="radio-group-item"]:has([data-slot="radio-group-item-input"][data-checked]) - + [data-slot="radio-group-item"]::before { - opacity: 0; + [data-slot="radio-group-item-input"] { + border-width: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + white-space: nowrap; + width: 1px; } [data-slot="radio-group-item-label"] { color: var(--text-weak); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; font-family: var(--font-family-sans); font-size: var(--font-size-small); font-weight: var(--font-weight-medium); - border-radius: var(--radius-md); - cursor: pointer; - display: flex; - flex-wrap: nowrap; - gap: calc(var(--spacing) * 1); + flex: 1; + height: 100%; line-height: 1; - padding: 6px 12px; - place-content: center; + padding: var(--radio-group-padding); position: relative; - transition-duration: 150ms; - transition-property: color, opacity; - transition-timing-function: ease-in-out; + transition: + color 200ms ease-out, + opacity 200ms ease-out; user-select: none; } - [data-slot="radio-group-item-input"] { - all: unset; + [data-slot="radio-group-item-control"] { + align-items: center; + border-radius: var(--radius-xs); + display: inline-flex; + height: 100%; + justify-content: center; + min-width: 0; + padding: var(--radio-group-control-padding, 0 10px); + transition: background-color 200ms ease-out; + width: 100%; + } + + &[data-pad="none"] { + --radio-group-control-padding: 0; + } + + &[data-pad="normal"] { + --radio-group-control-padding: 0 10px; } /* Checked state */ @@ -87,28 +123,26 @@ color: var(--text-strong); } + /* Hover state: match the inset background (adds subtle density) */ + [data-slot="radio-group-item-input"]:not([data-checked], [data-disabled]) + + [data-slot="radio-group-item-label"]:hover + [data-slot="radio-group-item-control"] { + background-color: var(--surface-inset-base); + } + + /* Do not overlay hover on the active segment */ + [data-slot="radio-group-item-input"][data-checked] + + [data-slot="radio-group-item-label"]:hover + [data-slot="radio-group-item-control"] { + background-color: transparent; + } + /* Disabled state */ [data-slot="radio-group-item-input"][data-disabled] + [data-slot="radio-group-item-label"] { cursor: not-allowed; opacity: 0.5; } - /* Hover state for unchecked, enabled items */ - [data-slot="radio-group-item-input"]:not([data-checked], [data-disabled]) + [data-slot="radio-group-item-label"] { - cursor: pointer; - user-select: none; - } - - [data-slot="radio-group-item-input"]:not([data-checked], [data-disabled]) - + [data-slot="radio-group-item-label"]:hover { - color: var(--text-base); - } - - [data-slot="radio-group-item-input"]:not([data-checked], [data-disabled]) - + [data-slot="radio-group-item-label"]:active { - opacity: 0.7; - } - /* Focus state */ [data-slot="radio-group-wrapper"]:has([data-slot="radio-group-item-input"]:focus-visible) [data-slot="radio-group-indicator"] { @@ -126,27 +160,23 @@ flex-direction: column; } - &[aria-orientation="vertical"] [data-slot="radio-group-item"]:not(:first-of-type)::before { - height: 1px; - width: auto; - inset: 0 6px; - transform: translateY(-0.5px); - } - /* Small size variant */ &[data-size="small"] { + --radio-group-height: 24px; + --radio-group-gap: 3px; + --radio-group-padding: 2px; + [data-slot="radio-group-item-label"] { font-size: 12px; - padding: 4px 8px; } - [data-slot="radio-group-item"]:not(:first-of-type)::before { - inset: 4px 0; + [data-slot="radio-group-item-control"] { + padding: var(--radio-group-control-padding, 0 8px); } + } - &[aria-orientation="vertical"] [data-slot="radio-group-item"]:not(:first-of-type)::before { - inset: 0 4px; - } + &[data-size="small"][data-pad="normal"] { + --radio-group-control-padding: 0 8px; } /* Disabled root state */ diff --git a/packages/ui/src/components/radio-group.tsx b/packages/ui/src/components/radio-group.tsx index e1812d61a..544e852e4 100644 --- a/packages/ui/src/components/radio-group.tsx +++ b/packages/ui/src/components/radio-group.tsx @@ -15,6 +15,8 @@ export type RadioGroupProps = Omit< class?: ComponentProps<"div">["class"] classList?: ComponentProps<"div">["classList"] size?: "small" | "medium" + fill?: boolean + pad?: "none" | "normal" } export function RadioGroup(props: RadioGroupProps) { @@ -28,6 +30,8 @@ export function RadioGroup(props: RadioGroupProps) { "label", "onSelect", "size", + "fill", + "pad", ]) const getValue = (item: T): string => { @@ -49,6 +53,8 @@ export function RadioGroup(props: RadioGroupProps) { {...others} data-component="radio-group" data-size={local.size ?? "medium"} + data-fill={local.fill ? "" : undefined} + data-pad={local.pad ?? "normal"} classList={{ ...(local.classList ?? {}), [local.class ?? ""]: !!local.class, @@ -62,9 +68,11 @@ export function RadioGroup(props: RadioGroupProps) {
{(option) => ( - + - {getLabel(option)} + + {getLabel(option)} + )}