mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
## What kind of change does this PR introduce? Refactor. Follow-up to #49871. ## What is the current behavior? Branded (green) text still uses the `text-brand` classname while the colour comes from `--primary`. ## What is the new behavior? **Rename-only:** `text-brand` → `text-primary` across callsites and docs. Leftover `bg-brand` / `border-brand` alias to `brand-default`. No intentional colour changes in this PR. This better matches how we treat our green in other components and props, like `variant="primary"` for green buttons. ## To test On light mode: smoke-test that branded text still looks like #49871 (readable green, not the bright fill). - [Homepage](https://zone-www-dot-com-git-dnywh-depr-316-text-brand-de2380-supabase.vercel.app/): “Scale to millions” uses `text-primary` - [Docs homepage](https://docs-git-dnywh-depr-316-text-brand-to-primary-supabase.vercel.app/docs): branded links like “More on self-hosting” are still readable - [Typography docs](https://design-system-git-dnywh-depr-316-text-brand-to-primary-supabase.vercel.app/design-system/docs/typography): documents `text-primary` - [Colour usage](https://design-system-git-dnywh-depr-316-text-brand-to-primary-supabase.vercel.app/design-system/docs/color-usage): `text-primary` is visibly darker than `bg-brand-default` _on light mode_ - [Studio auth providers](https://studio-staging-git-dnywh-depr-316-text-brand-to-60fa6c-supabase.vercel.app/dashboard/project/_/auth/providers): enabled provider badge text readable; status dot stays bright green <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Style** * Updated branded text, link hover states, icons, badges, indicators, and highlighted content across the Design System, Docs, Studio, Learn, UI Library, and marketing experiences to use the primary theme color. * Updated syntax highlighting and table-of-contents styling for consistent primary-color presentation. * Refined brand color fallback behavior for bright fills and borders. * **Documentation** * Updated color-usage and typography guidance to recommend the primary text utility. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
250 lines
6.9 KiB
TypeScript
250 lines
6.9 KiB
TypeScript
import { zodResolver } from '@hookform/resolvers/zod'
|
|
import { Check, Copy, Pencil, Split, ThumbsDown, ThumbsUp, Trash2 } from 'lucide-react'
|
|
import { useEffect, useState, type PropsWithChildren } from 'react'
|
|
import { useForm } from 'react-hook-form'
|
|
import {
|
|
Button,
|
|
cn,
|
|
Form,
|
|
FormControl,
|
|
FormField,
|
|
Popover,
|
|
PopoverContent,
|
|
PopoverTrigger,
|
|
TextArea,
|
|
} from 'ui'
|
|
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
|
|
import * as z from 'zod'
|
|
|
|
import { ButtonTooltip } from '../ButtonTooltip'
|
|
|
|
export function MessageActions({
|
|
children,
|
|
alwaysShow = false,
|
|
}: PropsWithChildren<{ alwaysShow?: boolean }>) {
|
|
return (
|
|
<div className="w-full max-w-3xl mx-auto flex items-center gap-4 mt-2 mb-1">
|
|
<span className="h-0.5 w-5 bg-muted" />
|
|
<div className={cn('group-hover:opacity-100 transition-opacity', !alwaysShow && 'opacity-0')}>
|
|
{children}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
function MessageActionsEdit({ onClick, tooltip }: { onClick: () => void; tooltip: string }) {
|
|
return (
|
|
<ButtonTooltip
|
|
variant="text"
|
|
icon={<Pencil size={14} strokeWidth={1.5} />}
|
|
onClick={onClick}
|
|
className="text-foreground-light hover:text-foreground p-1 rounded-sm"
|
|
aria-label={tooltip}
|
|
tooltip={{
|
|
content: {
|
|
side: 'bottom',
|
|
text: tooltip,
|
|
},
|
|
}}
|
|
/>
|
|
)
|
|
}
|
|
MessageActions.Edit = MessageActionsEdit
|
|
|
|
function MessageActionsDelete({ onClick }: { onClick: () => void }) {
|
|
return (
|
|
<ButtonTooltip
|
|
variant="text"
|
|
icon={<Trash2 size={14} strokeWidth={1.5} />}
|
|
tooltip={{ content: { side: 'bottom', text: 'Delete message' } }}
|
|
onClick={onClick}
|
|
className="text-foreground-light hover:text-foreground p-1 rounded-sm"
|
|
title="Delete message"
|
|
aria-label="Delete message"
|
|
/>
|
|
)
|
|
}
|
|
MessageActions.Delete = MessageActionsDelete
|
|
|
|
function MessageActionsBranch({ onClick }: { onClick: () => void }) {
|
|
return (
|
|
<ButtonTooltip
|
|
variant="text"
|
|
icon={<Split size={14} strokeWidth={1.5} />}
|
|
onClick={onClick}
|
|
className="text-foreground-light hover:text-foreground p-1 rounded-sm"
|
|
title="Branch in new chat"
|
|
aria-label="Branch in new chat"
|
|
tooltip={{ content: { side: 'bottom', text: 'Branch in new chat' } }}
|
|
/>
|
|
)
|
|
}
|
|
MessageActions.Branch = MessageActionsBranch
|
|
|
|
function MessageActionsCopy({ onClick }: { onClick: (onSuccess: () => void) => void }) {
|
|
const [copied, setCopied] = useState(false)
|
|
|
|
useEffect(() => {
|
|
if (copied) setTimeout(() => setCopied(false), 1000)
|
|
}, [copied])
|
|
|
|
return (
|
|
<ButtonTooltip
|
|
variant="text"
|
|
icon={copied ? <Check size={14} strokeWidth={2} /> : <Copy size={14} strokeWidth={1.5} />}
|
|
onClick={() => onClick(() => setCopied(true))}
|
|
className="text-foreground-light hover:text-foreground p-1 rounded-sm"
|
|
title="Copy response"
|
|
aria-label="Copy response"
|
|
tooltip={{ content: { side: 'bottom', text: 'Copy response' } }}
|
|
/>
|
|
)
|
|
}
|
|
MessageActions.Copy = MessageActionsCopy
|
|
|
|
function MessageActionsThumbsUp({
|
|
onClick,
|
|
isActive,
|
|
disabled,
|
|
}: {
|
|
onClick: () => void
|
|
isActive?: boolean
|
|
disabled?: boolean
|
|
}) {
|
|
return (
|
|
<Button
|
|
variant="text"
|
|
disabled={disabled}
|
|
icon={
|
|
<ThumbsUp
|
|
size={14}
|
|
strokeWidth={1.5}
|
|
className={cn(
|
|
isActive
|
|
? 'text-primary hover:text-brand-700'
|
|
: 'text-foreground-light hover:text-foreground'
|
|
)}
|
|
/>
|
|
}
|
|
onClick={onClick}
|
|
className={cn(
|
|
'p-1 rounded-sm transition-colors',
|
|
disabled && 'opacity-50 pointer-events-none'
|
|
)}
|
|
title="Good response"
|
|
aria-label="Good response"
|
|
/>
|
|
)
|
|
}
|
|
MessageActions.ThumbsUp = MessageActionsThumbsUp
|
|
|
|
const feedbackSchema = z.object({
|
|
reason: z.string().optional(),
|
|
})
|
|
|
|
type FeedbackFormValues = z.infer<typeof feedbackSchema>
|
|
|
|
function MessageActionsThumbsDown({
|
|
onClick,
|
|
isActive,
|
|
disabled,
|
|
}: {
|
|
onClick: (reason?: string) => void
|
|
isActive?: boolean
|
|
disabled?: boolean
|
|
}) {
|
|
const [open, setOpen] = useState(false)
|
|
|
|
const form = useForm<FeedbackFormValues>({
|
|
resolver: zodResolver(feedbackSchema),
|
|
defaultValues: { reason: '' },
|
|
mode: 'onSubmit',
|
|
})
|
|
|
|
const handleOpenChange = (newOpen: boolean) => {
|
|
if (disabled) return
|
|
// When popover closes, submit the rating if not already submitted
|
|
if (!newOpen && open && !form.formState.isSubmitSuccessful) {
|
|
onClick()
|
|
}
|
|
setOpen(newOpen)
|
|
if (!newOpen) {
|
|
form.reset()
|
|
}
|
|
}
|
|
|
|
const onSubmit = (values: FeedbackFormValues) => {
|
|
onClick(values.reason || undefined)
|
|
}
|
|
|
|
// Auto-close popover after showing thank you message
|
|
useEffect(() => {
|
|
if (form.formState.isSubmitSuccessful) {
|
|
const timer = setTimeout(() => {
|
|
setOpen(false)
|
|
}, 2000)
|
|
return () => clearTimeout(timer)
|
|
}
|
|
}, [form.formState.isSubmitSuccessful])
|
|
|
|
return (
|
|
<Popover open={open} onOpenChange={handleOpenChange}>
|
|
<PopoverTrigger asChild>
|
|
<Button
|
|
variant="text"
|
|
disabled={disabled}
|
|
onClick={() => !disabled && setOpen(true)}
|
|
className={cn(
|
|
'p-1 rounded-sm transition-colors',
|
|
disabled && 'opacity-50 pointer-events-none'
|
|
)}
|
|
title="Bad response"
|
|
aria-label="Bad response"
|
|
>
|
|
<ThumbsDown
|
|
size={14}
|
|
strokeWidth={1.5}
|
|
className={cn(
|
|
isActive
|
|
? 'text-warning hover:text-warning-700'
|
|
: 'text-foreground-light hover:text-foreground'
|
|
)}
|
|
/>
|
|
</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent className="w-80" align="start">
|
|
{form.formState.isSubmitSuccessful ? (
|
|
<p className="text-sm">We appreciate your feedback!</p>
|
|
) : (
|
|
<Form {...form}>
|
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-3">
|
|
<FormField
|
|
control={form.control}
|
|
name="reason"
|
|
render={({ field }) => (
|
|
<FormItemLayout label="What went wrong?" labelOptional="optional">
|
|
<FormControl>
|
|
<TextArea
|
|
placeholder="Describe why the response was not helpful..."
|
|
autoComplete="off"
|
|
rows={4}
|
|
autoFocus
|
|
{...field}
|
|
/>
|
|
</FormControl>
|
|
</FormItemLayout>
|
|
)}
|
|
/>
|
|
<div className="flex justify-end">
|
|
<Button variant="primary" type="submit" size="tiny">
|
|
Submit feedback
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
</Form>
|
|
)}
|
|
</PopoverContent>
|
|
</Popover>
|
|
)
|
|
}
|
|
MessageActions.ThumbsDown = MessageActionsThumbsDown
|