refactor: tokenize Chat menus, popovers, and message surfaces

This commit is contained in:
Marco Beretta 2026-06-20 16:07:47 +02:00
parent 1d0fcef327
commit ef3c08872b
No known key found for this signature in database
GPG key ID: D918033D8E74CC11
13 changed files with 40 additions and 42 deletions

View file

@ -87,7 +87,7 @@ export default function MCPConfigDialog({
render={({ field }) => {
const placeholder = localize('com_ui_mcp_enter_var', { 0: details.title });
const className =
'w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white sm:text-sm';
'w-full rounded-md border-border-medium shadow-sm focus:border-border-heavy focus:ring-ring-primary dark:bg-surface-tertiary dark:text-text-primary sm:text-sm';
if (details.sensitive === false) {
return (
<Input
@ -119,21 +119,23 @@ export default function MCPConfigDialog({
dangerouslySetInnerHTML={{ __html: sanitize(details.description) }}
/>
)}
{errors[key] && <p className="text-xs text-red-500">{errors[key]?.message}</p>}
{errors[key] && (
<p className="text-xs text-text-destructive">{errors[key]?.message}</p>
)}
</div>
))}
</form>
}
selection={{
selectHandler: handleSubmit(onFormSubmit),
selectClasses: 'bg-green-500 hover:bg-green-600 text-white',
selectClasses: 'bg-surface-submit text-white hover:bg-surface-submit-hover',
selectText: isSubmitting ? localize('com_ui_saving') : localize('com_ui_save'),
}}
buttons={
onRevoke && (
<Button
onClick={handleRevoke}
className="bg-red-600 text-white hover:bg-red-700 dark:hover:bg-red-800"
className="bg-surface-destructive text-white hover:bg-surface-destructive-hover"
disabled={isSubmitting}
>
{localize('com_ui_revoke')}

View file

@ -181,11 +181,11 @@ function MentionContent({
return (
<div className="absolute bottom-28 z-10 w-full space-y-2">
<div className="popover border-token-border-light rounded-2xl border bg-white p-2 shadow-lg dark:bg-gray-700">
<div className="popover border-token-border-light rounded-2xl border bg-surface-secondary p-2 shadow-lg">
<input
ref={initInputRef}
placeholder={localize(placeholder)}
className="mb-1 w-full border-0 bg-white p-2 text-sm focus:outline-none dark:bg-gray-700 dark:text-gray-200"
className="mb-1 w-full border-0 bg-surface-secondary p-2 text-sm text-text-primary focus:outline-none"
autoComplete="off"
value={searchValue}
onKeyDown={(e) => {

View file

@ -44,7 +44,7 @@ export default function OptionsPopover({
const localize = useLocalize();
const cardStyle =
'shadow-xl rounded-md min-w-[75px] font-normal bg-white border-black/10 border dark:bg-gray-700 text-black dark:text-white';
'shadow-xl rounded-md min-w-[75px] font-normal bg-surface-secondary border-border-light border text-text-primary';
if (!visible) {
return null;
@ -57,15 +57,14 @@ export default function OptionsPopover({
<div
className={cn(
cardStyle,
'dark:bg-gray-700',
'border-d-0 flex w-full flex-col overflow-hidden rounded-none border-s-0 border-t bg-white px-0 pb-[10px] dark:border-white/10 md:rounded-md md:border lg:w-[736px]',
'border-d-0 flex w-full flex-col overflow-hidden rounded-none border-s-0 border-t bg-surface-secondary px-0 pb-[10px] md:rounded-md md:border lg:w-[736px]',
)}
>
<div className="flex w-full items-center bg-gray-50 px-2 py-2 dark:bg-gray-700">
<div className="flex w-full items-center bg-surface-tertiary px-2 py-2">
{presetsDisabled ? null : (
<Button
type="button"
className="h-auto w-[150px] justify-start rounded-md border border-gray-300/50 bg-transparent px-2 py-1 text-xs font-normal text-black hover:bg-gray-100 hover:text-black focus-visible:ring-1 focus-visible:ring-ring-primary dark:border-gray-600 dark:bg-transparent dark:text-white dark:hover:bg-gray-600 dark:focus-visible:ring-white"
className="h-auto w-[150px] justify-start rounded-md border border-border-medium bg-transparent px-2 py-1 text-xs font-normal text-text-primary hover:bg-surface-hover focus-visible:ring-1 focus-visible:ring-ring-primary"
onClick={saveAsPreset}
>
<Save className="mr-1 w-[14px]" />
@ -76,7 +75,7 @@ export default function OptionsPopover({
<Button
type="button"
className={cn(
'ml-auto h-auto bg-transparent px-3 py-2 text-xs font-normal text-black hover:bg-gray-100 hover:text-black dark:bg-transparent dark:text-white dark:hover:bg-gray-700 dark:hover:text-white',
'ml-auto h-auto bg-transparent px-3 py-2 text-xs font-normal text-text-primary hover:bg-surface-hover',
removeFocusOutlines,
)}
onClick={closePopover}

View file

@ -102,8 +102,8 @@ export default function PopoverButtons({
type="button"
className={cn(
button.buttonClass,
'border border-gray-300/50 focus:ring-1 focus:ring-green-500/90 dark:border-gray-500/50 dark:focus:ring-green-500',
'ml-1 h-full bg-transparent px-2 py-1 text-xs font-normal text-black hover:bg-gray-100 hover:text-black dark:bg-transparent dark:text-white dark:hover:bg-gray-600 dark:hover:text-white',
'border border-border-medium focus:ring-1 focus:ring-ring-primary',
'ml-1 h-full bg-transparent px-2 py-1 text-xs font-normal text-text-primary hover:bg-surface-hover',
buttonClass ?? '',
)}
onClick={button.handler}
@ -121,8 +121,8 @@ export default function PopoverButtons({
type="button"
className={cn(
button.buttonClass,
'flex justify-center border border-gray-300/50 focus:ring-1 focus:ring-green-500/90 dark:border-gray-500/50 dark:focus:ring-green-500',
'h-full w-full bg-transparent px-2 py-1 text-xs font-normal text-black hover:bg-gray-100 hover:text-black dark:bg-transparent dark:text-white dark:hover:bg-gray-600 dark:hover:text-white',
'flex justify-center border border-border-medium focus:ring-1 focus:ring-ring-primary',
'h-full w-full bg-transparent px-2 py-1 text-xs font-normal text-text-primary hover:bg-surface-hover',
buttonClass ?? '',
)}
onClick={button.handler}

View file

@ -16,7 +16,7 @@ import ConvoIcon from '~/components/Endpoints/ConvoIcon';
import { useLocalize, useAuthContext } from '~/hooks';
const containerClassName =
'shadow-stroke relative flex h-full items-center justify-center rounded-full bg-white dark:bg-presentation dark:text-white text-black dark:after:shadow-none ';
'shadow-stroke relative flex h-full items-center justify-center rounded-full bg-surface-primary dark:bg-presentation text-text-primary dark:after:shadow-none ';
function getTextSizeClass(text: string | undefined | null) {
if (!text) {
@ -178,7 +178,7 @@ export default function Landing({ centerFormOnLanding }: { centerFormOnLanding:
endpointsConfig={endpointsConfig}
containerClassName={containerClassName}
context="landing"
className="h-2/3 w-2/3 text-black dark:text-white"
className="h-2/3 w-2/3 text-text-primary"
size={41}
/>
{startupConfig?.showBirthdayIcon && (

View file

@ -127,7 +127,7 @@ export const CustomMenuSeparator = React.forwardRef<HTMLHRElement, Ariakit.MenuS
ref={ref}
{...props}
className={cn(
'my-0.5 h-0 w-full border-t border-slate-200 dark:border-slate-700',
'my-0.5 h-0 w-full border-t border-border-light',
props.className,
)}
/>
@ -170,7 +170,7 @@ export const CustomMenuItem = React.forwardRef<HTMLDivElement, CustomMenuItemPro
blurOnHoverEnd: false,
...props,
className: cn(
'relative flex cursor-default items-center gap-2 rounded-lg px-2 py-1 outline-none! scroll-m-1 scroll-mt-[calc(var(--combobox-height,0px)+var(--label-height,4px))] aria-disabled:opacity-25 data-[active-item]:bg-black/[0.075] data-[active-item]:text-black dark:data-[active-item]:bg-white/10 dark:data-[active-item]:text-white sm:text-sm min-w-0 w-full before:absolute before:left-0 before:top-1 before:bottom-1 before:w-0.5 before:bg-transparent before:rounded-full data-[active-item]:before:bg-black dark:data-[active-item]:before:bg-white',
'relative flex cursor-default items-center gap-2 rounded-lg px-2 py-1 outline-none! scroll-m-1 scroll-mt-[calc(var(--combobox-height,0px)+var(--label-height,4px))] aria-disabled:opacity-25 data-[active-item]:bg-black/[0.075] data-[active-item]:text-text-primary dark:data-[active-item]:bg-white/10 sm:text-sm min-w-0 w-full before:absolute before:left-0 before:top-1 before:bottom-1 before:w-0.5 before:bg-transparent before:rounded-full data-[active-item]:before:bg-text-primary',
props.className,
),
};

View file

@ -228,7 +228,7 @@ export function SearchResults({ results, localize, searchValue }: SearchResultsP
<div className="flex items-center gap-2">
{endpoint.icon && (
<div
className="flex items-center justify-center overflow-hidden rounded-full border border-gray-200 p-1 dark:border-gray-700"
className="flex items-center justify-center overflow-hidden rounded-full border border-border-light p-1"
style={{ borderRadius: '50%' }}
>
{endpoint.icon}

View file

@ -132,7 +132,7 @@ const EditPresetDialog = ({
return (
<OGDialog open={presetModalVisible} onOpenChange={handleOpenChange} triggerRef={triggerRef}>
<OGDialogContent className="h-[100dvh] max-h-[100dvh] w-full max-w-full overflow-y-auto bg-white dark:border-gray-700 dark:bg-gray-850 dark:text-gray-300 md:h-auto md:max-h-[90vh] md:max-w-[75vw] md:rounded-lg lg:max-w-[950px]">
<OGDialogContent className="h-[100dvh] max-h-[100dvh] w-full max-w-full overflow-y-auto bg-surface-dialog dark:border-border-light dark:text-text-secondary md:h-auto md:max-h-[90vh] md:max-w-[75vw] md:rounded-lg lg:max-w-[950px]">
<OGDialogTitle>
{localize('com_ui_edit_preset_title', { title: preset?.title })}
</OGDialogTitle>
@ -199,13 +199,13 @@ const EditPresetDialog = ({
<div className="flex justify-end gap-2 border-t border-border-medium pt-2 md:pt-4">
<button
onClick={exportPreset}
className="rounded-md border border-gray-300 bg-white px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700 md:px-4"
className="rounded-md border border-border-medium bg-surface-secondary px-3 py-2 text-sm font-medium text-text-primary hover:bg-surface-hover focus:outline-none focus:ring-2 focus:ring-ring-primary focus:ring-offset-2 md:px-4"
>
{localize('com_endpoint_export')}
</button>
<button
onClick={submitPreset}
className="rounded-md bg-green-500 px-3 py-2 text-sm font-medium text-white hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 md:px-4"
className="rounded-md bg-surface-submit px-3 py-2 text-sm font-medium text-white hover:bg-surface-submit-hover focus:outline-none focus:ring-2 focus:ring-ring-primary focus:ring-offset-2 md:px-4"
>
{localize('com_ui_save')}
</button>

View file

@ -53,7 +53,7 @@ const PresetItems: FC<{
<div className="flex h-full grow items-center justify-end gap-2">
<label
htmlFor="default-preset"
className="w-40 truncate rounded bg-transparent py-1 text-xs font-medium text-gray-600 transition-colors dark:bg-transparent dark:text-gray-300 sm:w-72"
className="w-40 truncate rounded bg-transparent py-1 text-xs font-medium text-text-secondary transition-colors sm:w-72"
>
{defaultPreset
? `${localize('com_endpoint_preset_default_item')} ${defaultPreset.title}`
@ -63,7 +63,7 @@ const PresetItems: FC<{
<DialogTrigger asChild>
<button
type="button"
className="mr-1 flex h-[32px] cursor-pointer items-center rounded bg-transparent px-2 py-1 text-xs font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-red-700 focus:ring-ring dark:bg-transparent dark:text-gray-300 dark:hover:bg-gray-700 dark:hover:text-red-700"
className="mr-1 flex h-[32px] cursor-pointer items-center rounded bg-transparent px-2 py-1 text-xs font-medium text-text-secondary transition-colors hover:bg-surface-hover hover:text-text-destructive focus:ring-ring"
aria-label={localize('com_ui_clear_all')}
>
<svg
@ -100,7 +100,8 @@ const PresetItems: FC<{
}
selection={{
selectHandler: clearAllPresets,
selectClasses: 'bg-red-600 hover:bg-red-700 dark:hover:bg-red-600 text-white',
selectClasses:
'bg-surface-destructive text-white hover:bg-surface-destructive-hover',
selectText: localize('com_ui_clear'),
}}
/>
@ -114,7 +115,7 @@ const PresetItems: FC<{
className="pointer-none group m-1.5 flex h-8 min-w-[170px] gap-2 rounded px-5 py-2.5 !pr-3 text-sm !opacity-100 focus:ring-0 radix-disabled:pointer-events-none radix-disabled:opacity-50 md:min-w-[240px]"
tabIndex={-1}
>
<div className="flex h-full grow items-center justify-end gap-2 text-gray-600 dark:text-gray-300">
<div className="flex h-full grow items-center justify-end gap-2 text-text-secondary">
{/* TODO: Create Preset from here */}
{localize('com_endpoint_no_presets')}
</div>
@ -151,7 +152,7 @@ const PresetItems: FC<{
<Icon
context="menu-item"
iconURL={getEndpointField(endpointsConfig, preset.endpoint, 'iconURL')}
className="icon-md mr-1 dark:text-white"
className="icon-md mr-1 dark:text-text-primary"
endpoint={preset.endpoint}
/>
)
@ -174,7 +175,7 @@ const PresetItems: FC<{
render={
<button
className={cn(
'm-0 h-full rounded-md bg-transparent p-2 text-gray-400 hover:text-gray-700 focus:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 dark:focus:text-gray-200',
'm-0 h-full rounded-md bg-transparent p-2 text-text-tertiary hover:text-text-primary focus:text-text-primary',
defaultPreset?.presetId === presetId
? ''
: 'sm:invisible sm:group-focus-within:visible sm:group-hover:visible',
@ -201,7 +202,7 @@ const PresetItems: FC<{
aria-label={localize('com_ui_edit')}
render={
<button
className="m-0 h-full rounded-md p-2 text-gray-400 hover:text-gray-700 focus:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 dark:focus:text-gray-200 sm:invisible sm:group-focus-within:visible sm:group-hover:visible"
className="m-0 h-full rounded-md p-2 text-text-tertiary hover:text-text-primary focus:text-text-primary sm:invisible sm:group-focus-within:visible sm:group-hover:visible"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
@ -224,7 +225,7 @@ const PresetItems: FC<{
aria-label={localize('com_ui_delete')}
render={
<button
className="m-0 h-full rounded-md p-2 text-gray-400 hover:text-gray-600 focus:text-gray-600 dark:text-gray-400 dark:hover:text-gray-200 dark:focus:text-gray-200 sm:invisible sm:group-focus-within:visible sm:group-hover:visible"
className="m-0 h-full rounded-md p-2 text-text-tertiary hover:text-text-primary focus:text-text-primary sm:invisible sm:group-focus-within:visible sm:group-hover:visible"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();

View file

@ -38,7 +38,7 @@ const MenuItem: FC<MenuItemProps> = ({
aria-label={title}
data-testid="chat-menu-item"
className={cn(
'group m-1.5 flex cursor-pointer gap-2 rounded px-5 py-2.5 !pr-3 text-sm !opacity-100 hover:bg-black/5 focus:ring-0 radix-disabled:pointer-events-none radix-disabled:opacity-50 dark:hover:bg-gray-600 md:min-w-[240px]',
'group m-1.5 flex cursor-pointer gap-2 rounded px-5 py-2.5 !pr-3 text-sm !opacity-100 hover:bg-surface-hover focus:ring-0 radix-disabled:pointer-events-none radix-disabled:opacity-50 md:min-w-[240px]',
className || '',
)}
tabIndex={0} // Change to 0 to make it focusable

View file

@ -4,7 +4,7 @@ const MenuSeparator: FC = () => (
<div
role="separator"
aria-orientation="horizontal"
className="my-1.5 border-b bg-gray-200 dark:border-gray-700"
className="my-1.5 border-b border-border-light"
/>
);

View file

@ -140,18 +140,14 @@ const MessageIndicator = memo(function MessageIndicator({
<span
className={cn(
'block rounded-full transition-all duration-200',
isActive
? 'h-1.5 w-1.5 bg-gray-800 dark:bg-gray-100'
: 'h-1 w-1 bg-gray-400 dark:bg-gray-500',
isActive ? 'h-1.5 w-1.5 bg-text-primary' : 'h-1 w-1 bg-border-heavy',
)}
/>
) : (
<span
className={cn(
'block w-full rounded-full transition-all duration-200',
isActive
? 'h-[5px] bg-gray-800 dark:bg-gray-100'
: 'h-[3px] bg-gray-400 dark:bg-gray-500',
isActive ? 'h-[5px] bg-text-primary' : 'h-[3px] bg-border-heavy',
)}
/>
)}

View file

@ -9,11 +9,11 @@ const MinimalMessages = React.forwardRef(
return (
<div
className={cn(
'relative flex w-full grow overflow-hidden bg-white dark:bg-gray-800',
'relative flex w-full grow overflow-hidden bg-surface-secondary',
props.className,
)}
>
<div className="transition-width relative h-full w-full flex-1 overflow-auto bg-white dark:bg-gray-800">
<div className="transition-width relative h-full w-full flex-1 overflow-auto bg-surface-secondary">
<div className="flex h-full flex-col" role="presentation" tabIndex={0}>
<div className="flex-1 overflow-hidden overflow-y-auto">
<div className="dark:gpt-dark-gray relative h-full">