Files
supabase/apps/studio/components/interfaces/Advisors/CreateRuleSheet.tsx
Danny White ec53175b8a refactor(ui): rename text-brand to text-primary (#50564)
## 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 -->
2026-09-22 16:19:03 +10:00

251 lines
8.5 KiB
TypeScript

import { zodResolver } from '@hookform/resolvers/zod'
import { useParams } from 'common'
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import { SubmitHandler, useForm, useWatch } from 'react-hook-form'
import { toast } from 'sonner'
import {
Button,
Form,
FormControl,
FormField,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
Separator,
Sheet,
SheetContent,
SheetFooter,
SheetHeader,
SheetSection,
SheetTitle,
Switch,
TextArea,
Tooltip,
TooltipContent,
TooltipTrigger,
} from 'ui'
import { Admonition } from 'ui-patterns/Admonition'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import * as z from 'zod'
import { LintInfo } from '../Linter/Linter.constants'
import { lintInfoMap } from '../Linter/Linter.utils'
import { generateRuleDescription } from './AdvisorRules.utils'
import { useLintRuleCreateMutation } from '@/data/lint/create-lint-rule-mutation'
import { useOrganizationMembersQuery } from '@/data/organizations/organization-members-query'
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
interface CreateRuleSheetProps {
lint?: LintInfo
open: boolean
onOpenChange: (value: boolean) => void
}
const FormSchema = z.object({
lint_name: z.string().optional(),
note: z.string().optional(),
assigned_to: z.string().optional(),
is_disabled: z.boolean(),
})
const defaultValues = {
lint_name: undefined,
note: undefined,
assigned_to: 'all',
is_disabled: true,
}
/**
* [Joshen] JFYI while the API supports adding rules on a category, I'm intentionally leaving that functionality out for now
* as the only use case for that would be to ignore _all_ lints in that category (which I'm not sure if that's what we want to advise users doing atm)
*
* (Spoken with Hieu) We'll eventually support granularity of Entity/Items as well, just not atm
*/
export const CreateRuleSheet = ({ lint, open, onOpenChange }: CreateRuleSheetProps) => {
const router = useRouter()
const { ref: projectRef } = useParams()
const routeCategory = router.pathname.split('/').pop()
const { data: organization } = useSelectedOrganizationQuery()
const { data: members = [] } = useOrganizationMembersQuery({ slug: organization?.slug })
const { mutate: createRule, isPending: isCreating } = useLintRuleCreateMutation({
onSuccess: (_, vars) => {
const ruleLint = vars.exception.lint_name
const ruleLintMeta = lintInfoMap.find((x) => x.name === ruleLint)
toast.success(`Successfully created new rule for ${ruleLintMeta?.title}`)
if (ruleLintMeta) {
if (!!routeCategory && routeCategory !== ruleLintMeta.category) {
router.push(
`/project/${projectRef}/advisors/rules/${ruleLintMeta.category}?lint=${ruleLintMeta.name}`
)
} else {
// setExpandedLint(ruleLintMeta?.name)
}
}
onOpenChange(false)
},
})
const formId = 'create-lint-rule-form'
const form = useForm<z.infer<typeof FormSchema>>({
mode: 'onBlur',
reValidateMode: 'onChange',
resolver: zodResolver(FormSchema),
defaultValues,
})
const [lint_name, assigned_to, is_disabled] = useWatch({
control: form.control,
name: ['lint_name', 'assigned_to', 'is_disabled'],
})
const onSubmit: SubmitHandler<z.infer<typeof FormSchema>> = async (values) => {
if (!projectRef) return console.error('Project ref is required')
createRule({
projectRef,
exception: {
...values,
lint_category: undefined,
lint_name: values.lint_name,
assigned_to: values.assigned_to === 'all' ? undefined : values.assigned_to,
},
})
}
useEffect(() => {
if (open) form.reset({ ...defaultValues, lint_name: lint?.name })
}, [open])
return (
<Sheet open={open} onOpenChange={onOpenChange}>
<SheetContent className="flex flex-col gap-0">
<SheetHeader className="shrink-0 flex items-center gap-4">
<SheetTitle>Create a rule for "{lint?.title}"</SheetTitle>
</SheetHeader>
<SheetSection className="overflow-auto grow px-0">
<Form {...form}>
<form
id={formId}
className="flex flex-col gap-y-4"
onSubmit={form.handleSubmit(onSubmit)}
>
<FormField
name="is_disabled"
control={form.control}
render={({ field }) => (
<FormItemLayout
layout="flex-row-reverse"
className="px-5"
label={`Disable this lint for ${assigned_to === 'all' ? 'project' : 'the assigned member'}`}
description="Toggles the visiblity of this lint in the Advisor reports"
>
<Tooltip>
<TooltipTrigger type="button">
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
disabled={field.disabled || assigned_to === 'all'}
/>
</FormControl>
</TooltipTrigger>
{assigned_to === 'all' && (
<TooltipContent side="bottom" className="w-72">
Assign this rule to a specific project member before toggling this option
off. This will then configure the rule to{' '}
<span className="text-primary">only be visible</span> to that member in
the advisor reports.
</TooltipContent>
)}
</Tooltip>
</FormItemLayout>
)}
/>
<Separator />
<FormField
name="assigned_to"
control={form.control}
render={({ field }) => (
<FormItemLayout label="Assign rule to" layout="vertical" className="px-5">
<Select
onValueChange={(val) => {
field.onChange(val)
if (val === 'all') form.setValue('is_disabled', true)
}}
defaultValue={field.value}
>
<SelectTrigger className="col-span-8">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All project members</SelectItem>
{members.map((m) => (
<SelectItem key={m.gotrue_id} value={m.gotrue_id}>
{m.username || m.primary_email}
</SelectItem>
))}
</SelectContent>
</Select>
</FormItemLayout>
)}
/>
{!!lint_name && (
<div className="px-5">
<Admonition showIcon={false} type="default">
{generateRuleDescription({
name: lint_name,
disabled: is_disabled ?? defaultValues.is_disabled,
member: members.find((x) => x.gotrue_id === assigned_to),
})}
</Admonition>
</div>
)}
<Separator />
<FormField
name="note"
control={form.control}
render={({ field }) => (
<FormItemLayout
layout="vertical"
className="px-5"
label="Description"
labelOptional="Optional"
>
<FormControl>
<TextArea
{...field}
rows={4}
className="text-sm"
placeholder="e.g Describe why this rule is being set"
/>
</FormControl>
</FormItemLayout>
)}
/>
</form>
</Form>
</SheetSection>
<SheetFooter>
<Button disabled={isCreating} onClick={() => onOpenChange(false)}>
Cancel
</Button>
<Button variant="primary" form={formId} type="submit" loading={isCreating}>
Create rule
</Button>
</SheetFooter>
</SheetContent>
</Sheet>
)
}