introduce ui rules (#38728)
This commit is contained in:
253
.cursor/rules/studio-ui.mdc
Normal file
253
.cursor/rules/studio-ui.mdc
Normal file
@@ -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 (
|
||||
<ScaffoldContainer>
|
||||
<MyPageComponent />
|
||||
</ScaffoldContainer>
|
||||
)
|
||||
}
|
||||
|
||||
MyPage.getLayout = (page) => (
|
||||
<DefaultLayout>
|
||||
<AuthLayout>{page}</AuthLayout>
|
||||
</DefaultLayout>
|
||||
)
|
||||
|
||||
export default MyPage
|
||||
|
||||
export const MyPageComponent = () => (
|
||||
<ScaffoldSection isFullWidth>
|
||||
<div>
|
||||
<ScaffoldSectionTitle>My page section</ScaffoldSectionTitle>
|
||||
<ScaffoldSectionDescription>A brief description of the purpose of the page</ScaffoldSectionDescription>
|
||||
</div>
|
||||
// Content goes here
|
||||
</ScaffoldSection>
|
||||
)
|
||||
```
|
||||
|
||||
## 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<z.infer<typeof profileSchema>>({
|
||||
resolver: zodResolver(profileSchema),
|
||||
defaultValues: { username: '' },
|
||||
mode: 'onSubmit',
|
||||
reValidateMode: 'onBlur',
|
||||
})
|
||||
|
||||
function onSubmit(values: z.infer<typeof profileSchema>) {
|
||||
// handle values
|
||||
}
|
||||
|
||||
return (
|
||||
<Form_Shadcn_ {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||
<Card>
|
||||
<CardContent className="space-y-6">
|
||||
<FormField_Shadcn_
|
||||
control={form.control}
|
||||
name="username"
|
||||
render={({ field }) => (
|
||||
<FormItemLayout
|
||||
layout="flex-row-reverse"
|
||||
label="Username"
|
||||
description="This is your public display name."
|
||||
>
|
||||
<FormControl_Shadcn_>
|
||||
<Input_Shadcn_ placeholder="shadcn" autoComplete="off" {...field} />
|
||||
</FormControl_Shadcn_>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
<CardFooter className="justify-end">
|
||||
<Button type="primary" htmlType="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</form>
|
||||
</Form_Shadcn_>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## 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'
|
||||
|
||||
;<Table>
|
||||
<TableCaption>A list of your recent invoices.</TableCaption>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[100px]">Invoice</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead>Method</TableHead>
|
||||
<TableHead className="text-right">Amount</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell className="font-medium">INV001</TableCell>
|
||||
<TableCell>Paid</TableCell>
|
||||
<TableCell>Credit Card</TableCell>
|
||||
<TableCell className="text-right">$250.00</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
```
|
||||
|
||||
## 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
|
||||
<Admonition
|
||||
type="note"
|
||||
title="No authentication logs available for this user"
|
||||
description="Auth events such as logging in will be shown here"
|
||||
/>
|
||||
```
|
||||
Reference in New Issue
Block a user