Files
Danny White 476d4a5851 refactor(ui): drop redundant Button variant="default" props (#50161)
## What kind of change does this PR introduce?

Mechanical cleanup on top of the Button default-variant change (#50160).

## What is the current behavior?

Many callsites still pass `variant="default"` even though that is now
the component default.

## What is the new behavior?

Removes redundant static `variant="default"` from legacy `Button` and
`ButtonTooltip` callsites. Keeps explicit defaults where they document
the API:

- `button-default.tsx` and `button-sizes.tsx` demos
- `DocsButton`, which pins neutral styling at the wrapper boundary

## To test

Studio:

- [Auth → Rate
Limits](https://studio-staging-2s957kwc4-supabase.vercel.app/dashboard/project/_/auth/rate-limits):
dirty the form so Cancel appears; Cancel stays neutral, Save stays green
- [Project Settings → API
Keys](https://studio-staging-2s957kwc4-supabase.vercel.app/dashboard/project/_/settings/api-keys):
`DocsButton` in the header actions stays neutral

Design system:

- [Design system →
Button](https://design-system-git-dnywh-dc924ac1-supabase.vercel.app/design-system/docs/components/button):
`button-default` / `button-sizes` still show explicit default styling;
Primary (green) is restricted to the Primary section (and `asChild`)

WWW:

- [www → Brand
assets](https://zone-www-dot-com-git-dnywh-dc924ac1-supabase.vercel.app/brand-assets):
Download logo kit / Download button kit stay neutral
2026-09-11 17:05:26 +10:00

148 lines
4.8 KiB
TypeScript

import { zodResolver } from '@hookform/resolvers/zod'
import { Eye, EyeOff } from 'lucide-react'
import { useState } from 'react'
import { useForm, useWatch } from 'react-hook-form'
import { toast } from 'sonner'
import {
Button,
CardContent,
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogSection,
DialogTitle,
Form,
FormControl,
FormField,
Input,
} from 'ui'
import { Input as InputWithActions } from 'ui-patterns/DataInputs/Input'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { z } from 'zod'
import PasswordConditionsHelper from '@/components/interfaces/SignIn/PasswordConditionsHelper'
import { IdentityProviderIcon } from '@/components/ui/ProviderIcon'
import { useSetPasswordMutation } from '@/data/profile/profile-set-password-mutation'
import { getProviderDisplay } from '@/lib/external-identity-providers'
import { passwordValidation } from '@/lib/password-validation'
const FORM_ID = 'add-password-form'
const EMAIL_INPUT_ID = 'add-password-email'
const FormSchema = z.object({ password: passwordValidation })
type FormValues = z.infer<typeof FormSchema>
const defaultValues: FormValues = { password: '' }
/**
* Offers a user without an email identity (OAuth-only account) a way to create one by setting a
* password.
*
* Auth creates the email identity for the user's current email when the password is
* saved.
*/
export const AddPasswordRow = ({ email }: { email: string }) => {
const [isDialogOpen, setIsDialogOpen] = useState(false)
const providerDisplay = getProviderDisplay('email')
return (
<>
<CardContent className="flex justify-between items-center py-4">
<div className="flex gap-x-4">
<IdentityProviderIcon display={providerDisplay} size={30} />
<div>
<p className="text-sm">{providerDisplay.displayName}</p>
<p className="text-sm text-foreground-lighter">{email}</p>
</div>
</div>
<Button onClick={() => setIsDialogOpen(true)}>Add password</Button>
</CardContent>
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogContent>
<DialogHeader className="border-b">
<DialogTitle>Add password</DialogTitle>
</DialogHeader>
<AddPasswordForm email={email} onClose={() => setIsDialogOpen(false)} />
</DialogContent>
</Dialog>
</>
)
}
const AddPasswordForm = ({ email, onClose }: { email: string; onClose: () => void }) => {
const [passwordHidden, setPasswordHidden] = useState(true)
const form = useForm<FormValues>({
resolver: zodResolver(FormSchema),
defaultValues,
mode: 'onChange',
})
const password = useWatch({ control: form.control, name: 'password' })
const { mutate: setPassword, isPending } = useSetPasswordMutation({
onSuccess: () => {
toast.success('Password added successfully')
onClose()
},
})
const onSubmit = (values: FormValues) => setPassword({ password: values.password })
return (
<Form {...form}>
<form id={FORM_ID} onSubmit={form.handleSubmit(onSubmit)}>
<DialogSection className="flex flex-col gap-y-4">
<FormItemLayout
isReactForm={false}
id={EMAIL_INPUT_ID}
label="Email"
description="Adding a password lets you sign in with this email address"
>
<Input id={EMAIL_INPUT_ID} disabled value={email} />
</FormItemLayout>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItemLayout label="Password">
<FormControl>
<InputWithActions
id="password"
type={passwordHidden ? 'password' : 'text'}
placeholder="&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;"
disabled={isPending}
autoComplete="new-password"
actions={
<Button
icon={passwordHidden ? <Eye /> : <EyeOff />}
aria-label={passwordHidden ? 'Show password' : 'Hide Password'}
className="w-7"
onClick={() => setPasswordHidden((prev) => !prev)}
/>
}
{...field}
/>
</FormControl>
</FormItemLayout>
)}
/>
<PasswordConditionsHelper password={password} />
</DialogSection>
<DialogFooter>
<Button disabled={isPending} onClick={onClose}>
Cancel
</Button>
<Button variant="primary" type="submit" loading={isPending} disabled={isPending}>
Add password
</Button>
</DialogFooter>
</form>
</Form>
)
}