Files
supabase/apps/studio/components/interfaces/Settings/General/DashboardPreferences.tsx
Danny White 4c148ea060 chore(studio): move short Admonitions to descriptions (#46049)
## What kind of change does this PR introduce?

Chore. Follow-up to DEPR-551, #45302, #45535, and #45618.

## What is the current behaviour?

Some short Studio Admonitions still put their entire message in `title`
or legacy `label`, so body-copy callouts render as headings.

## What is the new behaviour?

Moves selected single-message Studio Admonitions to `description`,
keeping the follow-up deliberately limited to Studio callsites.

This PR does not touch Docs content, shared Alert styling, ui-patterns,
design-system registry/docs, or Tailwind config.

| Before | After |
| --- | --- |
| <img width="1818" height="388" alt="Image"
src="https://github.com/user-attachments/assets/283a1853-348a-4d74-a408-013957350e5e"
/> | <img width="1380" height="462" alt="Image"
src="https://github.com/user-attachments/assets/e5761e8e-3697-423b-805b-45110205099a"
/> |
| <img width="1640" height="716" alt="CleanShot 2026-04-28 at 15 17
25@2x"
src="https://github.com/user-attachments/assets/a5be4d5f-2bf7-4dc2-b396-56129fe64ec9"
/> | <img width="1630" height="716" alt="CleanShot 2026-04-28 at 15 16
00@2x"
src="https://github.com/user-attachments/assets/0d589252-aaf8-4efc-9d81-15ec4f99ec61"
/> |


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Style**
* Refined message displays and admonition styling across settings,
database, dashboard, and admin interfaces for improved visual
consistency and clarity.

* **UI Updates**
* Updated search input layouts and form element styling in publications
tables and other admin pages.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46049?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-19 10:20:54 +10:00

207 lines
7.2 KiB
TypeScript

import { zodResolver } from '@hookform/resolvers/zod'
import { LOCAL_STORAGE_KEYS, useParams } from 'common'
import { HelpCircle } from 'lucide-react'
import { useForm } from 'react-hook-form'
import { toast } from 'sonner'
import {
Button,
Card,
CardContent,
CardFooter,
Dialog,
DialogClose,
DialogContent,
DialogFooter,
DialogHeader,
DialogSection,
DialogSectionSeparator,
DialogTitle,
DialogTrigger,
Form,
FormControl,
FormField,
} from 'ui'
import { Admonition } from 'ui-patterns'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import {
PageSection,
PageSectionContent,
PageSectionMeta,
PageSectionSummary,
PageSectionTitle,
} from 'ui-patterns/PageSection'
import ShimmeringLoader, { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
import * as z from 'zod'
import { DatabaseSelector } from '@/components/ui/DatabaseSelector'
import { InlineLink } from '@/components/ui/InlineLink'
import { useReadReplicasQuery } from '@/data/read-replicas/replicas-query'
import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage'
const formSchema = z.object({
defaultDatabase: z.string().optional(),
})
export type DashboardPreference = z.infer<typeof formSchema>
const DEFAULT_PREFERENCE: DashboardPreference = {
defaultDatabase: undefined,
}
/**
* [Joshen] JFYI am not convinced about the UX of this, will iterate over time
* and only release to public when we're satisfied with how it behaves
* - Where should "Dashboard preferences" live
* - Preferences currently only apply to the user via local storage until we have middleware support that will persist the setting on the project for all users
* - Should selecting which database to run queries for dashboard be an option for users to configure (or for us to just default to)
* - I'd love for this to work seamlessly, but main concern atm is latency which is region dependent
* - Also, current database logic only applies to Table Editor atm, will need to extend it further to other pages
*/
export const DashboardPreferences = () => {
const { ref: projectRef } = useParams()
const [dashboardPreferences, setDashboardPreferences, { isLoading }] =
useLocalStorageQuery<DashboardPreference>(
LOCAL_STORAGE_KEYS.DASHBOARD_PREFERENCES(projectRef ?? '_'),
DEFAULT_PREFERENCE
)
const { isPending } = useReadReplicasQuery({ projectRef })
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: dashboardPreferences,
values: dashboardPreferences,
mode: 'onSubmit',
reValidateMode: 'onBlur',
})
const onSubmit = async (values: DashboardPreference) => {
if (!projectRef) return console.error('Ref is required')
setDashboardPreferences(values)
form.reset(values)
toast.success('Successfully saved dashboard preferences!')
}
return (
<PageSection>
<PageSectionMeta>
<PageSectionSummary>
<PageSectionTitle id="queries">Queries</PageSectionTitle>
</PageSectionSummary>
</PageSectionMeta>
<PageSectionContent className="flex flex-col gap-y-4">
{/* [Joshen] Ideally we're able to persist this for all users in the project, but will need support in our middleware */}
<Admonition
type="note"
description="These preferences control only your experience in the dashboard. Other members of this project will not be affected."
/>
{isLoading ? (
<Card>
<CardContent>
<GenericSkeletonLoader />
</CardContent>
</Card>
) : (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<Card>
<CardContent>
<FormField
control={form.control}
name="defaultDatabase"
render={({ field }) => (
<FormItemLayout
layout="flex-row-reverse"
label="Preferred database for dashboard queries"
description={
<p>
All read queries from the dashboard will run against the selected
database by default
<DashboardQueriesDialog />
</p>
}
className="[&>div]:md:w-1/2"
>
{isPending ? (
<ShimmeringLoader />
) : (
<FormControl>
{/* [Joshen] Need to disable unhealthy replicas */}
<DatabaseSelector
isForm
buttonProps={{ size: 'small', className: 'w-full' }}
selectedDatabaseId={field.value ?? projectRef}
onSelectId={(id) =>
field.onChange(id === projectRef ? undefined : id)
}
/>
</FormControl>
)}
</FormItemLayout>
)}
/>
</CardContent>
<CardFooter className="justify-end space-x-2">
{form.formState.isDirty && (
<Button
type="default"
htmlType="button"
onClick={() => form.reset(dashboardPreferences)}
>
Cancel
</Button>
)}
<Button type="primary" htmlType="submit" disabled={!form.formState.isDirty}>
Save changes
</Button>
</CardFooter>
</Card>
</form>
</Form>
)}
</PageSectionContent>
</PageSection>
)
}
const DashboardQueriesDialog = () => {
const { ref } = useParams()
return (
<Dialog>
<DialogTrigger className="ml-1 translate-y-0.5">
<HelpCircle size={14} className="hover:text-foreground transition" />
</DialogTrigger>
<DialogContent aria-describedby={undefined}>
<DialogHeader>
<DialogTitle>How does the dashboard interact with your project's database?</DialogTitle>
</DialogHeader>
<DialogSectionSeparator />
<DialogSection className="flex flex-col gap-y-2">
<p className="text-sm">
The dashboard queries your project's database to display data across various interfaces,
such as the <InlineLink href={`/project/${ref}/editor`}>Table Editor</InlineLink>, the{' '}
<InlineLink href={`/project/${ref}/auth/users`}>Auth Users</InlineLink> page, and more.
</p>
<p className="text-sm">
You can route these queries to a read replica instead, which will help reduce load on
your primary database.
</p>
</DialogSection>
<DialogFooter>
<DialogClose asChild className="opacity-100">
<Button type="default">Understood</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
)
}