diff --git a/.cursor/rules/studio-ui.mdc b/.cursor/rules/studio-ui.mdc new file mode 100644 index 0000000000..67db62a0ba --- /dev/null +++ b/.cursor/rules/studio-ui.mdc @@ -0,0 +1,253 @@ +--- +description: How to generate pages and interfaces in Studio, a web interface for managing Supabase projects +globs: +alwaysApply: true +--- + +## Project Structure + +- Next.js app using pages router +- Pages go in @apps/studio/pages + - Project related pages go in @apps/studio/pages/projects/[ref] + - Organization related pages go in @apps/studio/pages/org/[slug] +- Studio specific components go in @apps/studio/components + - Studio specific generic UI components go in @apps/studio/components/ui + - Studio specific components related to individual pages go in @apps/studio/components/interfaces e.g. @apps/studio/components/interfaces/Auth +- Generic helper functions go in @apps/studio/lib +- Generic hooks go in @apps/studio/hooks + +## Component system + +Our primitive component system is in @packages/ui and is based off shadcn/ui components. These components can be shared across all @apps e.g. studio and docs. Do not introduce new ui components unless asked to. + +- UI components are imported from this package across apps e.g. import { Button, Badge } from 'ui' +- Some components have a _Shadcn_ namespace appended to component name e.g. import { Input*Shadcn* } from 'ui' +- We should be using _Shadcn_ components where possible +- Before composing interfaces, read @packages/ui/index.tsx file for a full list of available components + +## Styling + +We use Tailwind for styling. + +- You should never use tailwind classes for colours and instead use classes we've defined ourselves + - Backgrounds // most of the time you will not need to define a background + - 'bg' used for main app surface background + - 'bg-muted' for elevating content // you can use Card instead + - 'bg-warning' for highlighting information that needs to be acted on + - 'bg-destructive' for highlighting issues + - Text + - 'text-foreground' for primary text like headings + - 'text-foreground-light' for body text + - 'text-foreground-lighter' for subtle text + - 'text-warning' for calling out information that needs action + - 'text-destructive' for calling out when something went wrong +- When needing to apply typography styles, read @apps/studio/styles/typography.scss and use one of the available classes instead of hard coding classes e.g. use "heading-default" instead of "text-sm font-medium" + +## Page structure + +When creating a new page follow these steps: + +- Create the page in @apps/studio/pages +- Use the PageLayout component that has the following props + + ```jsx + export interface NavigationItem { + id?: string + label: string + href?: string + icon?: ReactNode + onClick?: () => void + badge?: string + active?: boolean + } + + interface PageLayoutProps { + children?: ReactNode + title?: string | ReactNode + subtitle?: string | ReactNode + icon?: ReactNode + breadcrumbs?: Array<{ + label?: string + href?: string + element?: ReactNode + }> + primaryActions?: ReactNode + secondaryActions?: ReactNode + navigationItems?: NavigationItem[] + className?: string + size?: 'default' | 'full' | 'large' | 'small' + isCompact?: boolean + } + ``` + +- If a page has page related actions, add them to primary and secondary action props e.g. Users page has "Create new user" action +- If a page is within an existing section (e.g. Auth), you should use the related layout component e.g. AuthLayout +- Create a new component in @apps/studio/components/interfaces for the contents of the page +- Use ScaffoldContainer if the page should be center aligned in a container +- Use ScaffoldSection, ScaffoldSectionTitle, ScaffoldSectionDescription if the page has multiple sections + +### Page example + +```jsx +import { MyPageComponent } from 'components/interfaces/MyPage/MyPageComponent' +import AuthLayout from './AuthLayout' +import DefaultLayout from 'components/layouts/DefaultLayout' +import { ScaffoldContainer } from 'components/layouts/Scaffold' +import type { NextPageWithLayout } from 'types' + +const MyPage: NextPageWithLayout = () => { + return ( + + + + ) +} + +MyPage.getLayout = (page) => ( + + {page} + +) + +export default MyPage + +export const MyPageComponent = () => ( + +
+ My page section + A brief description of the purpose of the page +
+ // Content goes here +
+) +``` + +## Forms + +- Build forms with `react-hook-form` + `zod`. +- Use our `_Shadcn_` form primitives from `ui` and prefer `FormItemLayout` with layout="flex-row-reverse" for most controls (see `apps/studio/components/interfaces/Settings/Integrations/GithubIntegration/GitHubIntegrationConnectionForm.tsx`). +- Keep imports from `ui` with `_Shadcn_` suffixes. +- Forms should generally be wrapped in a Card unless specified + +### Example (single field) + +```tsx +import { zodResolver } from '@hookform/resolvers/zod' +import { useForm } from 'react-hook-form' +import * as z from 'zod' + +import { Button, Form_Shadcn_, FormField_Shadcn_, FormControl_Shadcn_, Input_Shadcn_ } from 'ui' +import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' + +const profileSchema = z.object({ + username: z.string().min(2, 'Username must be at least 2 characters'), +}) + +export function ProfileForm() { + const form = useForm>({ + resolver: zodResolver(profileSchema), + defaultValues: { username: '' }, + mode: 'onSubmit', + reValidateMode: 'onBlur', + }) + + function onSubmit(values: z.infer) { + // handle values + } + + return ( + +
+ + + ( + + + + + + )} + /> + + + + + +
+
+ ) +} +``` + +## Cards + +- Use cards when needing to group related pieces of information +- Cards can have sections with CardContent +- Use CardFooter for actions +- Only use CardHeader and CardTitle if the card content has not been described by the surrounding content e.g. Page title or ScaffoldSectionTitle +- Use CardHeader and CardTitle when you are using multiple Cards to group related pieces of content e.g. Primary branch, Persistent branches, Preview branches + +## Sheets + +- Use a sheet when needing to reveal more complicated forms or information relating to an object and context switching away to a new page would be disruptive e.g. we list auth providers, clicking an auth provider opens a sheet with information about that provider and a form to enable, user can close sheet to go back to providers list + +## Tables + +- Use the generic ui table components for most tables +- Tables are generally contained witin a card +- If a table has associated actions, they should go above on right hand side +- If a table has associated search or filters, they should go above on left hand side +- If a table is the main content of a page, and it does not have search or filters, you can add table actions to primary and secondary actions of PageLayout +- If a table is the main content of a page section, and it does not have search or filters, you can add table actions to the right of ScaffoldSectionTitle +- For simple lists of objects you can use ResourceList with ResourceListItem instead + +### Table example + +```jsx +import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from 'ui' + +; + A list of your recent invoices. + + + Invoice + Status + Method + Amount + + + + + INV001 + Paid + Credit Card + $250.00 + + +
+``` + +## Alerts + +- Use Admonition component to alert users of important actions or restrictions in place +- Place the Admonition either at the top of the contents of the page (below page title) or at the top of the related ScaffoldSection , below ScaffoldTitle +- Use sparingly + +### Alert example + +```jsx + +```