design system documentation

This commit is contained in:
Saxon Fletcher committed 2025-11-07 07:56:51 +10:00
1 parent 6f610be8cd
commit 8ed0198f63
42 files changed
+1487 -204

No files matched your search

+154
View File
@@ -38,6 +38,39 @@ export const Index: Record<string, any> = {
subcategory: "undefined",
chunks: []
},
"PageContainer": {
name: "PageContainer",
type: "components:fragment",
registryDependencies: undefined,
component: React.lazy(() => import("@/../../packages/ui-patterns/src/PageContainer")),
source: "",
files: ["registry/default//PageContainer/index.tsx"],
category: "undefined",
subcategory: "undefined",
chunks: []
},
"PageHeader": {
name: "PageHeader",
type: "components:fragment",
registryDependencies: undefined,
component: React.lazy(() => import("@/../../packages/ui-patterns/src/PageHeader")),
source: "",
files: ["registry/default//PageHeader/index.tsx"],
category: "undefined",
subcategory: "undefined",
chunks: []
},
"PageSection": {
name: "PageSection",
type: "components:fragment",
registryDependencies: undefined,
component: React.lazy(() => import("@/../../packages/ui-patterns/src/PageSection")),
source: "",
files: ["registry/default//PageSection/index.tsx"],
category: "undefined",
subcategory: "undefined",
chunks: []
},
"accordion-demo": {
name: "accordion-demo",
type: "components:example",
@@ -1886,6 +1919,127 @@ export const Index: Record<string, any> = {
subcategory: "undefined",
chunks: []
},
"page-components-integrated": {
name: "page-components-integrated",
type: "components:example",
registryDependencies: undefined,
component: React.lazy(() => import("@/registry/default/example/page-components-integrated")),
source: "",
files: ["registry/default/example/page-components-integrated.tsx"],
category: "undefined",
subcategory: "undefined",
chunks: []
},
"page-container-demo": {
name: "page-container-demo",
type: "components:example",
registryDependencies: undefined,
component: React.lazy(() => import("@/registry/default/example/page-container-demo")),
source: "",
files: ["registry/default/example/page-container-demo.tsx"],
category: "undefined",
subcategory: "undefined",
chunks: []
},
"page-layout-detail": {
name: "page-layout-detail",
type: "components:example",
registryDependencies: undefined,
component: React.lazy(() => import("@/registry/default/example/page-layout-detail")),
source: "",
files: ["registry/default/example/page-layout-detail.tsx"],
category: "undefined",
subcategory: "undefined",
chunks: []
},
"page-layout-full-page": {
name: "page-layout-full-page",
type: "components:example",
registryDependencies: undefined,
component: React.lazy(() => import("@/registry/default/example/page-layout-full-page")),
source: "",
files: ["registry/default/example/page-layout-full-page.tsx"],
category: "undefined",
subcategory: "undefined",
chunks: []
},
"page-layout-list": {
name: "page-layout-list",
type: "components:example",
registryDependencies: undefined,
component: React.lazy(() => import("@/registry/default/example/page-layout-list")),
source: "",
files: ["registry/default/example/page-layout-list.tsx"],
category: "undefined",
subcategory: "undefined",
chunks: []
},
"page-layout-list-simple": {
name: "page-layout-list-simple",
type: "components:example",
registryDependencies: undefined,
component: React.lazy(() => import("@/registry/default/example/page-layout-list-simple")),
source: "",
files: ["registry/default/example/page-layout-list-simple.tsx"],
category: "undefined",
subcategory: "undefined",
chunks: []
},
"page-layout-settings": {
name: "page-layout-settings",
type: "components:example",
registryDependencies: undefined,
component: React.lazy(() => import("@/registry/default/example/page-layout-settings")),
source: "",
files: ["registry/default/example/page-layout-settings.tsx"],
category: "undefined",
subcategory: "undefined",
chunks: []
},
"page-header-demo": {
name: "page-header-demo",
type: "components:example",
registryDependencies: undefined,
component: React.lazy(() => import("@/registry/default/example/page-header-demo")),
source: "",
files: ["registry/default/example/page-header-demo.tsx"],
category: "undefined",
subcategory: "undefined",
chunks: []
},
"page-section-demo": {
name: "page-section-demo",
type: "components:example",
registryDependencies: undefined,
component: React.lazy(() => import("@/registry/default/example/page-section-demo")),
source: "",
files: ["registry/default/example/page-section-demo.tsx"],
category: "undefined",
subcategory: "undefined",
chunks: []
},
"page-section-horizontal": {
name: "page-section-horizontal",
type: "components:example",
registryDependencies: undefined,
component: React.lazy(() => import("@/registry/default/example/page-section-horizontal")),
source: "",
files: ["registry/default/example/page-section-horizontal.tsx"],
category: "undefined",
subcategory: "undefined",
chunks: []
},
"page-section-with-aside": {
name: "page-section-with-aside",
type: "components:example",
registryDependencies: undefined,
component: React.lazy(() => import("@/registry/default/example/page-section-with-aside")),
source: "",
files: ["registry/default/example/page-section-with-aside.tsx"],
category: "undefined",
subcategory: "undefined",
chunks: []
},
"inner-side-menu-demo": {
name: "inner-side-menu-demo",
type: "components:example",
+1
View File
@@ -1,4 +1,5 @@
import '@/styles/globals.css'
import '../../studio/styles/typography.scss'
import type { Metadata } from 'next'
import { ThemeProvider } from './Providers'
import { SonnerToaster } from './SonnerToast'
@@ -120,11 +120,11 @@ export function ComponentPreview({
)
}, [Preview, align])
const wideClasses = wide ? '2xl:-ml-12 2xl:-mr-12' : ''
const wideClasses = wide ? '2xl:-ml-32 2xl:-mr-32' : ''
if (peekCode) {
return (
<div className={cn('mt-4 mb-12', wideClasses)}>
<div className={cn('@container mt-4 mb-12', wideClasses)}>
<div
className={cn(
'relative rounded-tl-md rounded-tr-md border-t border-l border-r bg-studio'
+20
View File
@@ -55,6 +55,11 @@ export const docsConfig: DocsConfig = {
href: '/docs/ui-patterns/navigation',
items: [],
},
{
title: 'Page',
href: '/docs/ui-patterns/page',
items: [],
},
{
title: 'Empty States',
href: '/docs/ui-patterns/empty-states',
@@ -85,6 +90,21 @@ export const docsConfig: DocsConfig = {
href: '/docs/fragments/modal',
items: [],
},
{
title: 'Page Container',
href: '/docs/fragments/page-container',
items: [],
},
{
title: 'Page Header',
href: '/docs/fragments/page-header',
items: [],
},
{
title: 'Page Section',
href: '/docs/fragments/page-section',
items: [],
},
{
title: 'Text Confirm Dialog',
href: '/docs/fragments/text-confirm-dialog',
@@ -0,0 +1,34 @@
---
title: Page Container
description: A container component that provides consistent max-width and padding based on size variants.
fragment: true
---
<ComponentPreview
name="page-container-demo"
description="Container with default size (1200px max-width)"
peekCode
wide
/>
## Usage
```tsx
import { PageContainer } from 'ui-patterns/PageContainer'
```
```tsx
<PageContainer size="default">
{/* Page content */}
</PageContainer>
```
## Size Variants
The `PageContainer` supports four size variants:
- `small` - max-width: 768px
- `default` - max-width: 1200px
- `large` - max-width: 1600px
- `full` - no max-width constraint
@@ -0,0 +1,23 @@
---
title: Page Header
description: A compound component for building page headers with consistent structure.
fragment: true
---
<ComponentPreview
name="page-header-demo"
description="Basic page header with title, description, and actions"
peekCode
wide
/>
## Sub-components
- `PageHeader.Root` - Root container with size variants (`default`, `small`, `large`, `full`)
- `PageHeader.Breadcrumb` - Breadcrumb navigation wrapper
- `PageHeader.Icon` - Icon container positioned left of title
- `PageHeader.Summary` - Container for title and description
- `PageHeader.Title` - Primary page heading (h1)
- `PageHeader.Description` - Supporting text below title
- `PageHeader.Aside` - Container for action buttons
- `PageHeader.Footer` - Container for tab navigation (NavMenu)
@@ -0,0 +1,46 @@
---
title: Page Section
description: A compound component for organizing page content into distinct sections.
fragment: true
---
<ComponentPreview
name="page-section-demo"
description="Basic page section with title, description, and content"
peekCode
wide
/>
## Sub-components
- `PageSection.Root` - Root container with orientation variants (`horizontal` or `vertical`)
- `PageSection.Summary` - Container for section title and description
- `PageSection.Title` - Section heading (h2)
- `PageSection.Description` - Supporting text below section title
- `PageSection.Aside` - Container for section-level actions
- `PageSection.Content` - Container for the main section content
## Orientation Variants
- `vertical` - Content stacks vertically (default)
- `horizontal` - Summary and content arranged horizontally on larger screens
## Examples
### Horizontal Orientation
<ComponentPreview
name="page-section-horizontal"
description="PageSection with horizontal layout - summary on left, content on right"
peekCode
wide
/>
### With Aside Actions
<ComponentPreview
name="page-section-with-aside"
description="PageSection with multiple action buttons in the Aside component"
peekCode
wide
/>
@@ -0,0 +1,80 @@
---
title: Page
description: Page components provide consistent structure and layout for page-level content.
---
The Page pattern consists of three main components that work together to create consistent page layouts: `PageContainer`, `PageHeader`, and `PageSection`. These components provide a structured approach to building pages with consistent spacing, max-widths, and content organization.
## Layouts
### Settings
Settings pages are used for configuration and preference management. They follow a single-column layout with default widths to keep content focused and readable.
- Use `PageHeader` with `size="default"`
- Use `PageContainer` with `size="default"`
- Use `PageSection` for organizing settings into logical groups
<ComponentPreview
name="page-layout-settings"
description="Settings page layout with default widths"
peekCode
wide
/>
### List
List pages display collections of objects like tables, triggers, or functions. These pages use larger widths to accommodate wide content like data tables.
- Use `PageHeader` with `size="large"`
- Use `PageContainer` with `size="large"`
- Use `PageSection` to wrap list content
**Table and List Actions:**
- **With filters or search:** If the table has filters or search, place table actions aligned with the filters on the right side. Do not use `PageSection.Aside` or `PageHeader.Aside` for table actions when filters are present.
- **Without filters:** For simple lists without filters or search, add primary list actions to `PageHeader.Aside` or `PageSection.Aside` as appropriate.
<ComponentPreview
name="page-layout-list"
description="List page layout with search and filters - actions aligned with search"
peekCode
wide
/>
<ComponentPreview
name="page-layout-list-simple"
description="Simple list page layout without filters - actions in PageHeader.Aside"
peekCode
wide
/>
### Data and Full Page Experiences
Full-page experiences like editors require maximum screen real estate. These pages use full-width containers to maximize available space.
- Use `PageHeader` with `size="full"`
- Use `PageContainer` with `size="full"`
- Content spans the full width of the viewport
### Detail Pages
Detail pages display dense or lengthy content split into multiple sections. The horizontal orientation allows for better information hierarchy and context.
- Use `PageHeader` with `size="large"`
- Use `PageContainer` with `size="large"`
- Use `PageSection` with `orientation="horizontal"` to show summary alongside content
- Multiple sections can stack vertically with horizontal layouts within each
<ComponentPreview
name="page-layout-detail"
description="Detail page layout with horizontal sections for dense content"
peekCode
wide
/>
## Components
- **[PageContainer](/docs/fragments/page-container)** - Container component providing consistent max-width and padding based on size variants
- **[PageHeader](/docs/fragments/page-header)** - Compound component for building page headers with breadcrumbs, icons, titles, descriptions, actions, and navigation
- **[PageSection](/docs/fragments/page-section)** - Compound component for organizing page content into distinct sections with title, description, and action areas
+1
View File
@@ -51,6 +51,7 @@
},
"devDependencies": {
"@shikijs/compat": "^1.1.7",
"@tailwindcss/container-queries": "^0.1.1",
"@types/lodash.template": "4.5.0",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
@@ -0,0 +1,45 @@
import { PageContainer } from 'ui-patterns/PageContainer'
import { PageHeader } from 'ui-patterns/PageHeader'
import { PageSection } from 'ui-patterns/PageSection'
import { Button, Card, CardContent } from 'ui'
export default function PageComponentsIntegrated() {
return (
<div className="w-full">
<PageHeader.Root size="large">
<PageHeader.Summary>
<PageHeader.Title>Edge Functions</PageHeader.Title>
<PageHeader.Description>
Manage and deploy serverless functions that run at the edge.
</PageHeader.Description>
</PageHeader.Summary>
<PageHeader.Aside>
<Button type="primary" size="small">
Deploy Function
</Button>
</PageHeader.Aside>
</PageHeader.Root>
<PageContainer size="large">
<PageSection.Root>
<PageSection.Summary>
<PageSection.Title>Function Details</PageSection.Title>
<PageSection.Description>
Configure your function settings and view logs.
</PageSection.Description>
</PageSection.Summary>
<PageSection.Content>
<Card>
<CardContent className="p-6">
<p className="text-sm text-foreground-light">
This demonstrates how PageContainer, PageHeader, and PageSection work together to
create a complete page layout.
</p>
</CardContent>
</Card>
</PageSection.Content>
</PageSection.Root>
</PageContainer>
</div>
)
}
@@ -0,0 +1,21 @@
import { PageContainer } from 'ui-patterns/PageContainer'
import { Button } from 'ui'
export default function PageContainerDemo() {
return (
<div className="w-full space-y-4">
<PageContainer size="small" className="bg-muted border rounded-lg p-4">
<p>This is a page container with small size (768px max-width)</p>
</PageContainer>
<PageContainer size="default" className="bg-muted border rounded-lg p-4">
<p>This is a page container with default size (1200px max-width)</p>
</PageContainer>
<PageContainer size="large" className="bg-muted border rounded-lg p-4">
<p>This is a page container with large size (1600px max-width)</p>
</PageContainer>
<PageContainer size="full" className="bg-muted border rounded-lg p-4">
<p>This is a page container with full size (no max-width)</p>
</PageContainer>
</div>
)
}
@@ -0,0 +1,76 @@
import React from 'react'
import Link from 'next/link'
import { PageHeader } from 'ui-patterns/PageHeader'
import {
Button,
NavMenu,
NavMenuItem,
BreadcrumbItem_Shadcn_ as BreadcrumbItem,
BreadcrumbLink_Shadcn_ as BreadcrumbLink,
BreadcrumbList_Shadcn_ as BreadcrumbList,
BreadcrumbSeparator_Shadcn_ as BreadcrumbSeparator,
} from 'ui'
import { Database } from 'lucide-react'
export default function PageHeaderDemo() {
const breadcrumbItems = [
{ label: 'Project', href: '/project/demo' },
{ label: 'Edge Functions', href: '/project/demo/functions' },
]
const navigationItems = [
{ label: 'Overview', href: '/project/demo/functions/demo-function' },
{ label: 'Invocations', href: '/project/demo/functions/demo-function/invocations' },
{ label: 'Logs', href: '/project/demo/functions/demo-function/logs' },
{ label: 'Details', href: '/project/demo/functions/demo-function/details' },
]
return (
<div className="w-full">
<PageHeader.Root>
<PageHeader.Breadcrumb>
<BreadcrumbList>
{breadcrumbItems.map((item, index) => (
<React.Fragment key={item.label}>
<BreadcrumbItem>
<BreadcrumbLink asChild>
<Link href={item.href}>{item.label}</Link>
</BreadcrumbLink>
</BreadcrumbItem>
{index < breadcrumbItems.length - 1 && <BreadcrumbSeparator />}
</React.Fragment>
))}
</BreadcrumbList>
</PageHeader.Breadcrumb>
<PageHeader.Icon>
<Database className="w-5 h-5" />
</PageHeader.Icon>
<PageHeader.Summary>
<PageHeader.Title>Demo Function</PageHeader.Title>
<PageHeader.Description>
Serverless functions that run at the edge with low latency and automatic scaling.
</PageHeader.Description>
</PageHeader.Summary>
<PageHeader.Aside>
<Button type="default" size="small">
Secondary
</Button>
<Button type="primary" size="small">
Deploy Function
</Button>
</PageHeader.Aside>
<PageHeader.Footer>
<NavMenu>
{navigationItems.map((item) => (
<NavMenuItem key={item.label}>
<Link href={item.href}>{item.label}</Link>
</NavMenuItem>
))}
</NavMenu>
</PageHeader.Footer>
</PageHeader.Root>
</div>
)
}
@@ -0,0 +1,115 @@
import { PageContainer } from 'ui-patterns/PageContainer'
import { PageHeader } from 'ui-patterns/PageHeader'
import { PageSection } from 'ui-patterns/PageSection'
import { Button, Card, CardContent } from 'ui'
export default function PageLayoutDetail() {
return (
<div className="w-full">
<PageHeader.Root size="large">
<PageHeader.Summary>
<PageHeader.Title>Billing</PageHeader.Title>
<PageHeader.Description>
Manage your organization's billing and subscription settings.
</PageHeader.Description>
</PageHeader.Summary>
</PageHeader.Root>
<PageContainer size="large">
<PageSection.Root orientation="horizontal">
<PageSection.Summary>
<PageSection.Title>Subscription</PageSection.Title>
<PageSection.Description>
View and manage your current subscription plan and billing cycle.
</PageSection.Description>
</PageSection.Summary>
<PageSection.Content>
<Card>
<CardContent className="p-6">
<div className="space-y-4">
<div>
<p className="text-xs text-foreground-lighter mb-1">Current Plan</p>
<p className="text-sm font-medium">Pro Plan</p>
</div>
<div>
<p className="text-xs text-foreground-lighter mb-1">Billing Cycle</p>
<p className="text-sm">Monthly</p>
</div>
<div>
<p className="text-xs text-foreground-lighter mb-1">Next Billing Date</p>
<p className="text-sm">March 15, 2024</p>
</div>
<div className="pt-2">
<Button type="default" size="small">
Change Plan
</Button>
</div>
</div>
</CardContent>
</Card>
</PageSection.Content>
</PageSection.Root>
<PageSection.Root orientation="horizontal">
<PageSection.Summary>
<PageSection.Title>Cost Control</PageSection.Title>
<PageSection.Description>
Set spending limits and alerts to manage your organization's costs.
</PageSection.Description>
</PageSection.Summary>
<PageSection.Content>
<Card>
<CardContent className="p-6">
<div className="space-y-4">
<div>
<p className="text-xs text-foreground-lighter mb-1">Monthly Spending Limit</p>
<p className="text-sm">$500.00</p>
</div>
<div>
<p className="text-xs text-foreground-lighter mb-1">Current Month Spend</p>
<p className="text-sm">$234.50</p>
</div>
<div className="pt-2">
<Button type="default" size="small">
Configure Limits
</Button>
</div>
</div>
</CardContent>
</Card>
</PageSection.Content>
</PageSection.Root>
<PageSection.Root orientation="horizontal">
<PageSection.Summary>
<PageSection.Title>Payment Methods</PageSection.Title>
<PageSection.Description>
Manage payment methods and billing information for your organization.
</PageSection.Description>
</PageSection.Summary>
<PageSection.Content>
<Card>
<CardContent className="p-6">
<div className="space-y-4">
<div>
<p className="text-xs text-foreground-lighter mb-1">Primary Payment Method</p>
<p className="text-sm">•••• •••• •••• 4242</p>
</div>
<div>
<p className="text-xs text-foreground-lighter mb-1">Expires</p>
<p className="text-sm">12/2025</p>
</div>
<div className="pt-2">
<Button type="default" size="small">
Update Payment Method
</Button>
</div>
</div>
</CardContent>
</Card>
</PageSection.Content>
</PageSection.Root>
</PageContainer>
</div>
)
}
@@ -0,0 +1,34 @@
import { PageContainer } from 'ui-patterns/PageContainer'
import { PageHeader } from 'ui-patterns/PageHeader'
import { Button } from 'ui'
export default function PageLayoutFullPage() {
return (
<div className="w-full">
<PageHeader.Root size="full">
<PageHeader.Summary>
<PageHeader.Title>SQL Editor</PageHeader.Title>
<PageHeader.Description>
Write and execute SQL queries with syntax highlighting and auto-complete.
</PageHeader.Description>
</PageHeader.Summary>
<PageHeader.Aside>
<Button type="default" size="small">
Format
</Button>
<Button type="primary" size="small">
Run Query
</Button>
</PageHeader.Aside>
</PageHeader.Root>
<PageContainer size="full">
<div className="mt-4 border rounded-lg bg-background p-4 min-h-[400px]">
<p className="text-sm text-foreground-light">
Editor content takes full width for maximum screen real estate.
</p>
</div>
</PageContainer>
</div>
)
}
@@ -0,0 +1,87 @@
import { PageContainer } from 'ui-patterns/PageContainer'
import { PageHeader } from 'ui-patterns/PageHeader'
import { PageSection } from 'ui-patterns/PageSection'
import {
Button,
Card,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from 'ui'
export default function PageLayoutListSimple() {
const items = [
{ id: 1, name: 'Project Alpha', status: 'Active', members: 12 },
{ id: 2, name: 'Project Beta', status: 'Active', members: 8 },
{ id: 3, name: 'Project Gamma', status: 'Inactive', members: 5 },
]
return (
<div className="w-full">
<PageHeader.Root size="large">
<PageHeader.Summary>
<PageHeader.Title>Projects</PageHeader.Title>
<PageHeader.Description>
Manage and view all your projects in one place.
</PageHeader.Description>
</PageHeader.Summary>
<PageHeader.Aside>
<Button type="primary" size="small">
Create Project
</Button>
</PageHeader.Aside>
</PageHeader.Root>
<PageContainer size="large">
<PageSection.Root>
<PageSection.Content>
<Card>
<Table>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Status</TableHead>
<TableHead>Members</TableHead>
<TableHead className="text-right">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{items.map((item) => (
<TableRow key={item.id}>
<TableCell className="font-medium">{item.name}</TableCell>
<TableCell>{item.status}</TableCell>
<TableCell>{item.members}</TableCell>
<TableCell className="text-right">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button type="text" size="small">
Actions
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Duplicate</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem className="text-destructive">Delete</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Card>
</PageSection.Content>
</PageSection.Root>
</PageContainer>
</div>
)
}
@@ -0,0 +1,118 @@
import { PageContainer } from 'ui-patterns/PageContainer'
import { PageHeader } from 'ui-patterns/PageHeader'
import { PageSection } from 'ui-patterns/PageSection'
import {
Button,
Card,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
Input,
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from 'ui'
import { Search } from 'lucide-react'
export default function PageLayoutList() {
const functions = [
{
id: 1,
name: 'get_user_profile',
arguments: 'user_id uuid',
return_type: 'jsonb',
security: 'Definer',
},
{
id: 2,
name: 'update_user_settings',
arguments: 'user_id uuid, settings jsonb',
return_type: 'void',
security: 'Invoker',
},
{
id: 3,
name: 'calculate_total',
arguments: 'amount numeric, tax_rate numeric',
return_type: 'numeric',
security: 'Definer',
},
]
return (
<div className="w-full">
<PageHeader.Root size="large">
<PageHeader.Summary>
<PageHeader.Title>Database Functions</PageHeader.Title>
<PageHeader.Description>Manage your database functions</PageHeader.Description>
</PageHeader.Summary>
</PageHeader.Root>
<PageContainer size="large">
<PageSection.Root>
<PageSection.Content>
<div className="w-full space-y-4">
<div className="flex flex-col lg:flex-row lg:items-center justify-between gap-2 flex-wrap">
<div className="flex flex-col lg:flex-row lg:items-center gap-2">
<Input
placeholder="Search for a function"
size="tiny"
icon={<Search size={14} />}
className="w-full lg:w-52"
/>
</div>
<Button type="primary">Create a new function</Button>
</div>
<Card>
<Table className="table-fixed overflow-x-auto">
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead className="table-cell">Arguments</TableHead>
<TableHead className="table-cell">Return type</TableHead>
<TableHead className="table-cell w-[100px]">Security</TableHead>
<TableHead className="w-1/6"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{functions.map((fn) => (
<TableRow key={fn.id}>
<TableCell className="font-medium">{fn.name}</TableCell>
<TableCell>{fn.arguments}</TableCell>
<TableCell>{fn.return_type}</TableCell>
<TableCell>{fn.security}</TableCell>
<TableCell>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button type="text" size="small">
Actions
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Duplicate</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem className="text-destructive">
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Card>
</div>
</PageSection.Content>
</PageSection.Root>
</PageContainer>
</div>
)
}
@@ -0,0 +1,234 @@
import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
import * as z from 'zod'
import { PageContainer } from 'ui-patterns/PageContainer'
import { PageHeader } from 'ui-patterns/PageHeader'
import { PageSection } from 'ui-patterns/PageSection'
import {
Button,
Card,
CardContent,
CardFooter,
FormControl_Shadcn_,
FormField_Shadcn_,
Form_Shadcn_,
Input_Shadcn_,
PrePostTab,
Switch,
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
const RefreshTokenSchema = z.object({
REFRESH_TOKEN_ROTATION_ENABLED: z.boolean(),
SECURITY_REFRESH_TOKEN_REUSE_INTERVAL: z.coerce.number().min(0),
})
const UserSessionsSchema = z.object({
SESSIONS_TIMEBOX: z.coerce.number().min(0),
SESSIONS_INACTIVITY_TIMEOUT: z.coerce.number().min(0),
SESSIONS_SINGLE_PER_USER: z.boolean(),
})
function HoursOrNeverText({ value }: { value: number }) {
if (value === 0) {
return 'never'
} else if (value === 1) {
return 'hour'
} else {
return 'hours'
}
}
export default function PageLayoutSettings() {
const refreshTokenForm = useForm<z.infer<typeof RefreshTokenSchema>>({
resolver: zodResolver(RefreshTokenSchema),
defaultValues: {
REFRESH_TOKEN_ROTATION_ENABLED: false,
SECURITY_REFRESH_TOKEN_REUSE_INTERVAL: 10,
},
})
const userSessionsForm = useForm<z.infer<typeof UserSessionsSchema>>({
resolver: zodResolver(UserSessionsSchema),
defaultValues: {
SESSIONS_TIMEBOX: 0,
SESSIONS_INACTIVITY_TIMEOUT: 0,
SESSIONS_SINGLE_PER_USER: false,
},
})
return (
<div className="w-full">
<PageHeader.Root size="default">
<PageHeader.Summary>
<PageHeader.Title>User Sessions</PageHeader.Title>
<PageHeader.Description>
Configure settings for user sessions and refresh tokens
</PageHeader.Description>
</PageHeader.Summary>
</PageHeader.Root>
<PageContainer size="default">
<PageSection.Root>
<PageSection.Summary>
<PageSection.Title>Refresh Tokens</PageSection.Title>
<PageSection.Description>
Configure refresh token rotation and security settings.
</PageSection.Description>
</PageSection.Summary>
<PageSection.Content>
<Form_Shadcn_ {...refreshTokenForm}>
<form className="space-y-4">
<Card>
<CardContent className="pt-6">
<FormField_Shadcn_
control={refreshTokenForm.control}
name="REFRESH_TOKEN_ROTATION_ENABLED"
render={({ field }) => (
<FormItemLayout
layout="flex-row-reverse"
label="Detect and revoke potentially compromised refresh tokens"
description="Prevent replay attacks from potentially compromised refresh tokens."
>
<FormControl_Shadcn_>
<Switch checked={field.value} onCheckedChange={field.onChange} />
</FormControl_Shadcn_>
</FormItemLayout>
)}
/>
</CardContent>
<CardContent>
<FormField_Shadcn_
control={refreshTokenForm.control}
name="SECURITY_REFRESH_TOKEN_REUSE_INTERVAL"
render={({ field }) => (
<FormItemLayout
layout="flex-row-reverse"
label="Refresh token reuse interval"
description="Time interval where the same refresh token can be used multiple times to request for an access token. Recommendation: 10 seconds."
>
<FormControl_Shadcn_>
<PrePostTab postTab="seconds">
<Input_Shadcn_ type="number" min={0} {...field} />
</PrePostTab>
</FormControl_Shadcn_>
</FormItemLayout>
)}
/>
</CardContent>
<CardFooter className="justify-end space-x-2">
{refreshTokenForm.formState.isDirty && (
<Button type="default" onClick={() => refreshTokenForm.reset()}>
Cancel
</Button>
)}
<Button
type="primary"
htmlType="submit"
disabled={!refreshTokenForm.formState.isDirty}
>
Save changes
</Button>
</CardFooter>
</Card>
</form>
</Form_Shadcn_>
</PageSection.Content>
</PageSection.Root>
<PageSection.Root>
<PageSection.Summary>
<PageSection.Title>User Sessions</PageSection.Title>
<PageSection.Description>
Configure session timeout and single session enforcement settings.
</PageSection.Description>
</PageSection.Summary>
<PageSection.Content>
<Form_Shadcn_ {...userSessionsForm}>
<form className="space-y-4">
<Card>
<CardContent>
<FormField_Shadcn_
control={userSessionsForm.control}
name="SESSIONS_SINGLE_PER_USER"
render={({ field }) => (
<FormItemLayout
layout="flex-row-reverse"
label="Enforce single session per user"
description="If enabled, all but a user's most recently active session will be terminated."
>
<FormControl_Shadcn_>
<Switch checked={field.value} onCheckedChange={field.onChange} />
</FormControl_Shadcn_>
</FormItemLayout>
)}
/>
</CardContent>
<CardContent>
<FormField_Shadcn_
control={userSessionsForm.control}
name="SESSIONS_TIMEBOX"
render={({ field }) => (
<FormItemLayout
layout="flex-row-reverse"
label="Time-box user sessions"
description="The amount of time before a user is forced to sign in again. Use 0 for never."
>
<div className="flex items-center">
<FormControl_Shadcn_>
<PrePostTab postTab={<HoursOrNeverText value={field.value || 0} />}>
<Input_Shadcn_ type="number" min={0} {...field} />
</PrePostTab>
</FormControl_Shadcn_>
</div>
</FormItemLayout>
)}
/>
</CardContent>
<CardContent>
<FormField_Shadcn_
control={userSessionsForm.control}
name="SESSIONS_INACTIVITY_TIMEOUT"
render={({ field }) => (
<FormItemLayout
layout="flex-row-reverse"
label="Inactivity timeout"
description="The amount of time a user needs to be inactive to be forced to sign in again. Use 0 for never."
>
<div className="flex items-center">
<FormControl_Shadcn_>
<PrePostTab postTab={<HoursOrNeverText value={field.value || 0} />}>
<Input_Shadcn_ type="number" {...field} />
</PrePostTab>
</FormControl_Shadcn_>
</div>
</FormItemLayout>
)}
/>
</CardContent>
<CardFooter className="justify-end space-x-2">
{userSessionsForm.formState.isDirty && (
<Button type="default" onClick={() => userSessionsForm.reset()}>
Cancel
</Button>
)}
<Button
type="primary"
htmlType="submit"
disabled={!userSessionsForm.formState.isDirty}
>
Save changes
</Button>
</CardFooter>
</Card>
</form>
</Form_Shadcn_>
</PageSection.Content>
</PageSection.Root>
</PageContainer>
</div>
)
}
@@ -0,0 +1,31 @@
import { PageSection } from 'ui-patterns/PageSection'
import { Button, Card, CardContent } from 'ui'
export default function PageSectionDemo() {
return (
<div className="w-full">
<PageSection.Root>
<PageSection.Summary>
<PageSection.Title>Section Title</PageSection.Title>
<PageSection.Description>
This is a section with a title and description, plus optional actions.
</PageSection.Description>
</PageSection.Summary>
<PageSection.Aside>
<Button type="default" size="small">
Action
</Button>
</PageSection.Aside>
<PageSection.Content>
<Card>
<CardContent className="p-6">
<p className="text-sm text-foreground-light">
Section content goes here. This could be forms, tables, or any other content.
</p>
</CardContent>
</Card>
</PageSection.Content>
</PageSection.Root>
</div>
)
}
@@ -0,0 +1,29 @@
import { PageSection } from 'ui-patterns/PageSection'
import { Card, CardContent } from 'ui'
export default function PageSectionHorizontal() {
return (
<div className="w-full">
<PageSection.Root orientation="horizontal">
<PageSection.Summary>
<PageSection.Title>Section Title</PageSection.Title>
<PageSection.Description>
In horizontal orientation, the summary (title and description) appears on the left, with
content arranged on the right on larger screens. This is useful for detailed sections
where you want to maintain context while viewing content.
</PageSection.Description>
</PageSection.Summary>
<PageSection.Content>
<Card>
<CardContent className="p-6">
<p className="text-sm text-foreground-light">
The content area appears alongside the summary in a horizontal layout. On smaller
screens, it will stack vertically.
</p>
</CardContent>
</Card>
</PageSection.Content>
</PageSection.Root>
</div>
)
}
@@ -0,0 +1,35 @@
import { PageSection } from 'ui-patterns/PageSection'
import { Button, Card, CardContent } from 'ui'
export default function PageSectionWithAside() {
return (
<div className="w-full">
<PageSection.Root>
<PageSection.Summary>
<PageSection.Title>Section Title</PageSection.Title>
<PageSection.Description>
This demonstrates PageSection with actions in the Aside component.
</PageSection.Description>
</PageSection.Summary>
<PageSection.Aside>
<Button type="default" size="small">
Secondary
</Button>
<Button type="primary" size="small">
Primary Action
</Button>
</PageSection.Aside>
<PageSection.Content>
<Card>
<CardContent className="p-6">
<p className="text-sm text-foreground-light">
The Aside component positions actions horizontally aligned with the section summary,
providing a clear separation between description and actions.
</p>
</CardContent>
</Card>
</PageSection.Content>
</PageSection.Root>
</div>
)
}
+50
View File
@@ -1078,6 +1078,56 @@ export const examples: Registry = [
type: 'components:example',
files: ['example/info-tooltip-demo.tsx'],
},
{
name: 'page-components-integrated',
type: 'components:example',
files: ['example/page-components-integrated.tsx'],
},
{
name: 'page-container-demo',
type: 'components:example',
files: ['example/page-container-demo.tsx'],
},
{
name: 'page-layout-detail',
type: 'components:example',
files: ['example/page-layout-detail.tsx'],
},
{
name: 'page-layout-full-page',
type: 'components:example',
files: ['example/page-layout-full-page.tsx'],
},
{
name: 'page-layout-list',
type: 'components:example',
files: ['example/page-layout-list.tsx'],
},
{
name: 'page-layout-settings',
type: 'components:example',
files: ['example/page-layout-settings.tsx'],
},
{
name: 'page-header-demo',
type: 'components:example',
files: ['example/page-header-demo.tsx'],
},
{
name: 'page-section-demo',
type: 'components:example',
files: ['example/page-section-demo.tsx'],
},
{
name: 'page-section-horizontal',
type: 'components:example',
files: ['example/page-section-horizontal.tsx'],
},
{
name: 'page-section-with-aside',
type: 'components:example',
files: ['example/page-section-with-aside.tsx'],
},
{
name: 'inner-side-menu-demo',
type: 'components:example',
+18
View File
@@ -19,4 +19,22 @@ export const fragments: Registry = [
files: ['/Dialogs/ConfirmDialog.tsx'],
optionalPath: '/Dialogs',
},
{
name: 'PageContainer',
type: 'components:fragment',
files: ['/PageContainer/index.tsx'],
optionalPath: '/PageContainer',
},
{
name: 'PageHeader',
type: 'components:fragment',
files: ['/PageHeader/index.tsx'],
optionalPath: '/PageHeader',
},
{
name: 'PageSection',
type: 'components:fragment',
files: ['/PageSection/index.tsx'],
optionalPath: '/PageSection',
},
]
+12 -1
View File
@@ -207,17 +207,28 @@ export const Index: Record<string, any> = {
// console.log('item', item)
let packagePath = ''
let importPath = ''
if (type === 'ui') {
packagePath = `../../packages/ui/src/components/shadcn/ui`
importPath = `${packagePath}/${item.name}`
}
if (type === 'fragment') {
packagePath = `../../packages/ui-patterns/src${item.optionalPath}`
// If the file is index.tsx, import from the directory directly (without appending component name)
const firstFile = item.files[0] || ''
if (firstFile.endsWith('/index.tsx')) {
importPath = packagePath
} else {
importPath = `${packagePath}/${item.name}`
}
}
if (type === 'example') {
packagePath = `registry/${style.name}/${type}`
importPath = `${packagePath}/${item.name}`
}
if (type === 'block') {
packagePath = `registry/${style.name}/${type}`
importPath = `${packagePath}/${item.name}`
}
index += `
@@ -225,7 +236,7 @@ export const Index: Record<string, any> = {
name: "${item.name}",
type: "${item.type}",
registryDependencies: ${JSON.stringify(item.registryDependencies)},
component: React.lazy(() => import("@/${packagePath}/${item.name}")),
component: React.lazy(() => import("@/${importPath}")),
source: "${sourceFilename}",
files: [${resolveFiles.map((file) => `"${file}"`)}],
category: "${item.category}",
+1
View File
@@ -10,6 +10,7 @@ module.exports = config({
'./../../packages/ui/src/**/*.{tsx,ts,js}',
'./../../packages/ui-patterns/src/**/*.{tsx,ts,js}',
],
plugins: [require('@tailwindcss/container-queries')],
theme: {
extend: {
maxWidth: {
@@ -2,7 +2,7 @@ import { ExternalLink } from 'lucide-react'
import Link from 'next/link'
import { useParams } from 'common'
import { PageSection } from 'components/ui/PageSection'
import { PageSection } from 'ui-patterns/PageSection'
import AlertError from 'components/ui/AlertError'
import { ResourceList } from 'components/ui/Resource/ResourceList'
import { HorizontalShimmerWithIcon } from 'components/ui/Shimmers'
@@ -8,7 +8,7 @@ import { toast } from 'sonner'
import { boolean, object, string } from 'yup'
import { useParams } from 'common'
import { PageSection } from 'components/ui/PageSection'
import { PageSection } from 'ui-patterns/PageSection'
import AlertError from 'components/ui/AlertError'
import { InlineLink } from 'components/ui/InlineLink'
import NoPermission from 'components/ui/NoPermission'
@@ -119,80 +119,112 @@ export const BasicAuthSettingsForm = () => {
<PageSection.Title>User Signups</PageSection.Title>
</PageSection.Summary>
<PageSection.Content>
{isError && (
<AlertError
error={authConfigError}
subject="Failed to retrieve auth configuration for hooks"
/>
)}
{isError && (
<AlertError
error={authConfigError}
subject="Failed to retrieve auth configuration for hooks"
/>
)}
{isPermissionsLoaded && !canReadConfig && (
<div className="mt-8">
<NoPermission resourceText="view auth configuration settings" />
</div>
)}
{isPermissionsLoaded && !canReadConfig && (
<div className="mt-8">
<NoPermission resourceText="view auth configuration settings" />
</div>
)}
{isLoading && (
<Card>
<CardContent className="py-6">
<ShimmeringLoader />
</CardContent>
<CardContent className="py-6">
<ShimmeringLoader />
</CardContent>
<CardContent className="py-6">
<ShimmeringLoader />
</CardContent>
<CardContent className="py-7">
<ShimmeringLoader />
</CardContent>
<CardContent className="py-7"></CardContent>
</Card>
)}
{isLoading && (
<Card>
<CardContent className="py-6">
<ShimmeringLoader />
</CardContent>
<CardContent className="py-6">
<ShimmeringLoader />
</CardContent>
<CardContent className="py-6">
<ShimmeringLoader />
</CardContent>
<CardContent className="py-7">
<ShimmeringLoader />
</CardContent>
<CardContent className="py-7"></CardContent>
</Card>
)}
{isSuccess && (
<Form_Shadcn_ {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<Card>
<CardContent>
<FormField_Shadcn_
control={form.control}
name="DISABLE_SIGNUP"
render={({ field }) => (
<FormItemLayout
layout="flex-row-reverse"
label="Allow new users to sign up"
description="If this is disabled, new users will not be able to sign up to your application"
>
<FormControl_Shadcn_>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
disabled={!canUpdateConfig}
/>
</FormControl_Shadcn_>
</FormItemLayout>
)}
/>
</CardContent>
{showManualLinking && (
{isSuccess && (
<Form_Shadcn_ {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<Card>
<CardContent>
<FormField_Shadcn_
control={form.control}
name="SECURITY_MANUAL_LINKING_ENABLED"
name="DISABLE_SIGNUP"
render={({ field }) => (
<FormItemLayout
layout="flex-row-reverse"
label="Allow manual linking"
label="Allow new users to sign up"
description="If this is disabled, new users will not be able to sign up to your application"
>
<FormControl_Shadcn_>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
disabled={!canUpdateConfig}
/>
</FormControl_Shadcn_>
</FormItemLayout>
)}
/>
</CardContent>
{showManualLinking && (
<CardContent>
<FormField_Shadcn_
control={form.control}
name="SECURITY_MANUAL_LINKING_ENABLED"
render={({ field }) => (
<FormItemLayout
layout="flex-row-reverse"
label="Allow manual linking"
description={
<>
Enable{' '}
<InlineLink
className="text-foreground-light hover:text-foreground"
href={`${DOCS_URL}/guides/auth/auth-identity-linking#manual-linking-beta`}
>
manual linking APIs
</InlineLink>{' '}
for your project
</>
}
>
<FormControl_Shadcn_>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
disabled={!canUpdateConfig}
/>
</FormControl_Shadcn_>
</FormItemLayout>
)}
/>
</CardContent>
)}
<CardContent>
<FormField_Shadcn_
control={form.control}
name="EXTERNAL_ANONYMOUS_USERS_ENABLED"
render={({ field }) => (
<FormItemLayout
layout="flex-row-reverse"
label="Allow anonymous sign-ins"
description={
<>
Enable{' '}
<InlineLink
className="text-foreground-light hover:text-foreground"
href={`${DOCS_URL}/guides/auth/auth-identity-linking#manual-linking-beta`}
href={`${DOCS_URL}/guides/auth/auth-anonymous`}
>
manual linking APIs
anonymous sign-ins
</InlineLink>{' '}
for your project
</>
@@ -208,132 +240,99 @@ export const BasicAuthSettingsForm = () => {
</FormItemLayout>
)}
/>
</CardContent>
)}
<CardContent>
<FormField_Shadcn_
control={form.control}
name="EXTERNAL_ANONYMOUS_USERS_ENABLED"
render={({ field }) => (
<FormItemLayout
layout="flex-row-reverse"
label="Allow anonymous sign-ins"
description={
<>
Enable{' '}
<InlineLink
className="text-foreground-light hover:text-foreground"
href={`${DOCS_URL}/guides/auth/auth-anonymous`}
>
anonymous sign-ins
</InlineLink>{' '}
for your project
</>
}
{form.watch('EXTERNAL_ANONYMOUS_USERS_ENABLED') && (
<Alert_Shadcn_
className="flex w-full items-center justify-between mt-4"
variant="warning"
>
<FormControl_Shadcn_>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
disabled={!canUpdateConfig}
/>
</FormControl_Shadcn_>
</FormItemLayout>
)}
/>
{form.watch('EXTERNAL_ANONYMOUS_USERS_ENABLED') && (
<Alert_Shadcn_
className="flex w-full items-center justify-between mt-4"
variant="warning"
>
<WarningIcon />
<div>
<AlertTitle_Shadcn_>
Anonymous users will use the <code className="text-xs">authenticated</code>{' '}
role when signing in
</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_ className="flex flex-col gap-y-3">
<p>
As a result, anonymous users will be subjected to RLS policies that apply
to the <code className="text-xs">public</code> and{' '}
<code className="text-xs">authenticated</code> roles. We strongly advise{' '}
<Link
href={`/project/${projectRef}/auth/policies`}
className="text-foreground underline"
>
reviewing your RLS policies
</Link>{' '}
to ensure that access to your data is restricted where required.
</p>
<Button asChild type="default" className="w-min" icon={<ExternalLink />}>
<Link href={`${DOCS_URL}/guides/auth/auth-anonymous#access-control`}>
View access control docs
</Link>
</Button>
</AlertDescription_Shadcn_>
</div>
</Alert_Shadcn_>
)}
{!authConfig?.SECURITY_CAPTCHA_ENABLED &&
form.watch('EXTERNAL_ANONYMOUS_USERS_ENABLED') && (
<Alert_Shadcn_ className="mt-4">
<WarningIcon />
<AlertTitle_Shadcn_>
We highly recommend{' '}
<InlineLink href={`/project/${projectRef}/auth/protection`}>
enabling captcha
</InlineLink>{' '}
for anonymous sign-ins
</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
This will prevent potential abuse on sign-ins which may bloat your database
and incur costs for monthly active users (MAU)
</AlertDescription_Shadcn_>
<div>
<AlertTitle_Shadcn_>
Anonymous users will use the{' '}
<code className="text-xs">authenticated</code> role when signing in
</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_ className="flex flex-col gap-y-3">
<p>
As a result, anonymous users will be subjected to RLS policies that
apply to the <code className="text-xs">public</code> and{' '}
<code className="text-xs">authenticated</code> roles. We strongly advise{' '}
<Link
href={`/project/${projectRef}/auth/policies`}
className="text-foreground underline"
>
reviewing your RLS policies
</Link>{' '}
to ensure that access to your data is restricted where required.
</p>
<Button asChild type="default" className="w-min" icon={<ExternalLink />}>
<Link href={`${DOCS_URL}/guides/auth/auth-anonymous#access-control`}>
View access control docs
</Link>
</Button>
</AlertDescription_Shadcn_>
</div>
</Alert_Shadcn_>
)}
</CardContent>
<CardContent>
<FormField_Shadcn_
control={form.control}
name="MAILER_AUTOCONFIRM"
render={({ field }) => (
<FormItemLayout
layout="flex-row-reverse"
label="Confirm email"
description="Users will need to confirm their email address before signing in for the first time"
>
<FormControl_Shadcn_>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
disabled={!canUpdateConfig}
/>
</FormControl_Shadcn_>
</FormItemLayout>
{!authConfig?.SECURITY_CAPTCHA_ENABLED &&
form.watch('EXTERNAL_ANONYMOUS_USERS_ENABLED') && (
<Alert_Shadcn_ className="mt-4">
<WarningIcon />
<AlertTitle_Shadcn_>
We highly recommend{' '}
<InlineLink href={`/project/${projectRef}/auth/protection`}>
enabling captcha
</InlineLink>{' '}
for anonymous sign-ins
</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
This will prevent potential abuse on sign-ins which may bloat your
database and incur costs for monthly active users (MAU)
</AlertDescription_Shadcn_>
</Alert_Shadcn_>
)}
</CardContent>
<CardContent>
<FormField_Shadcn_
control={form.control}
name="MAILER_AUTOCONFIRM"
render={({ field }) => (
<FormItemLayout
layout="flex-row-reverse"
label="Confirm email"
description="Users will need to confirm their email address before signing in for the first time"
>
<FormControl_Shadcn_>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
disabled={!canUpdateConfig}
/>
</FormControl_Shadcn_>
</FormItemLayout>
)}
/>
</CardContent>
<CardFooter className="justify-end space-x-2">
{form.formState.isDirty && (
<Button type="default" onClick={() => form.reset()}>
Cancel
</Button>
)}
/>
</CardContent>
<CardFooter className="justify-end space-x-2">
{form.formState.isDirty && (
<Button type="default" onClick={() => form.reset()}>
Cancel
<Button
type="primary"
htmlType="submit"
disabled={!canUpdateConfig || isUpdatingConfig || !form.formState.isDirty}
loading={isUpdatingConfig}
>
Save changes
</Button>
)}
<Button
type="primary"
htmlType="submit"
disabled={!canUpdateConfig || isUpdatingConfig || !form.formState.isDirty}
loading={isUpdatingConfig}
>
Save changes
</Button>
</CardFooter>
</Card>
</form>
</Form_Shadcn_>
)}
</CardFooter>
</Card>
</form>
</Form_Shadcn_>
)}
</PageSection.Content>
</PageSection.Root>
)
@@ -4,7 +4,7 @@ import { useState } from 'react'
import { toast } from 'sonner'
import { useParams } from 'common'
import { PageSection } from 'components/ui/PageSection'
import { PageSection } from 'ui-patterns/PageSection'
import AlertError from 'components/ui/AlertError'
import { DocsButton } from 'components/ui/DocsButton'
import { InlineLink } from 'components/ui/InlineLink'
@@ -51,8 +51,8 @@ import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import CommandRender from '../CommandRender'
import { INVOCATION_TABS } from './EdgeFunctionDetails.constants'
import { generateCLICommands } from './EdgeFunctionDetails.utils'
import { PageContainer } from 'components/ui/PageContainer'
import { PageSection } from 'components/ui/PageSection'
import { PageContainer } from 'ui-patterns/PageContainer'
import { PageSection } from 'ui-patterns/PageSection'
const FormSchema = z.object({
name: z.string().min(0, 'Name is required'),
@@ -9,7 +9,7 @@ import { BlobReader, BlobWriter, ZipWriter } from '@zip.js/zip.js'
import { useParams } from 'common'
import { useIsAPIDocsSidePanelEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { EdgeFunctionTesterSheet } from 'components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionTesterSheet'
import { PageHeader } from 'components/ui/PageHeader'
import { PageHeader } from 'ui-patterns/PageHeader'
import { APIDocsButton } from 'components/ui/APIDocsButton'
import { DocsButton } from 'components/ui/DocsButton'
import NoPermission from 'components/ui/NoPermission'
@@ -7,8 +7,8 @@ import { AuthProvidersForm } from 'components/interfaces/Auth/AuthProvidersForm'
import { BasicAuthSettingsForm } from 'components/interfaces/Auth/BasicAuthSettingsForm'
import AuthLayout from 'components/layouts/AuthLayout/AuthLayout'
import DefaultLayout from 'components/layouts/DefaultLayout'
import { PageContainer } from 'components/ui/PageContainer'
import { PageHeader } from 'components/ui/PageHeader'
import { PageContainer } from 'ui-patterns/PageContainer'
import { PageHeader } from 'ui-patterns/PageHeader'
import { NavMenu, NavMenuItem } from 'ui'
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
import { UnknownInterface } from 'components/ui/UnknownInterface'
@@ -8,8 +8,8 @@ import { useParams } from 'common'
import { ThirdPartyAuthForm } from 'components/interfaces/Auth/ThirdPartyAuthForm'
import AuthLayout from 'components/layouts/AuthLayout/AuthLayout'
import DefaultLayout from 'components/layouts/DefaultLayout'
import { PageContainer } from 'components/ui/PageContainer'
import { PageHeader } from 'components/ui/PageHeader'
import { PageContainer } from 'ui-patterns/PageContainer'
import { PageHeader } from 'ui-patterns/PageHeader'
import { NavMenu, NavMenuItem } from 'ui'
import NoPermission from 'components/ui/NoPermission'
import { UnknownInterface } from 'components/ui/UnknownInterface'
@@ -8,7 +8,7 @@ import DatabaseBackupsNav from 'components/interfaces/Database/Backups/DatabaseB
import DatabaseLayout from 'components/layouts/DatabaseLayout/DatabaseLayout'
import DefaultLayout from 'components/layouts/DefaultLayout'
import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold'
import { PageHeader } from 'components/ui/PageHeader'
import { PageHeader } from 'ui-patterns/PageHeader'
import AlertError from 'components/ui/AlertError'
import { DocsButton } from 'components/ui/DocsButton'
import InformationBox from 'components/ui/InformationBox'
@@ -9,8 +9,8 @@ import FunctionsList from 'components/interfaces/Database/Functions/FunctionsLis
import DatabaseLayout from 'components/layouts/DatabaseLayout/DatabaseLayout'
import DefaultLayout from 'components/layouts/DefaultLayout'
import { SIDEBAR_KEYS } from 'components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
import { PageContainer } from 'components/ui/PageContainer'
import { PageHeader } from 'components/ui/PageHeader'
import { PageContainer } from 'ui-patterns/PageContainer'
import { PageHeader } from 'ui-patterns/PageHeader'
import { DocsButton } from 'components/ui/DocsButton'
import NoPermission from 'components/ui/NoPermission'
import { DatabaseFunction } from 'data/database-functions/database-functions-query'
@@ -19,7 +19,7 @@ import { DOCS_URL } from 'lib/constants'
import { useEditorPanelStateSnapshot } from 'state/editor-panel-state'
import { useSidebarManagerSnapshot } from 'state/sidebar-manager-state'
import type { NextPageWithLayout } from 'types'
import { PageSection } from 'components/ui/PageSection'
import { PageSection } from 'ui-patterns/PageSection'
const DatabaseFunctionsPage: NextPageWithLayout = () => {
const [selectedFunction, setSelectedFunction] = useState<DatabaseFunction | undefined>()
@@ -1,8 +1,8 @@
import { EdgeFunctionDetails } from 'components/interfaces/Functions/EdgeFunctionDetails/EdgeFunctionDetails'
import DefaultLayout from 'components/layouts/DefaultLayout'
import EdgeFunctionDetailsLayout from 'components/layouts/EdgeFunctionsLayout/EdgeFunctionDetailsLayout'
import { PageContainer } from 'components/ui/PageContainer'
import { PageSection } from 'components/ui/PageSection'
import { PageContainer } from 'ui-patterns/PageContainer'
import { PageSection } from 'ui-patterns/PageSection'
import type { NextPageWithLayout } from 'types'
const PageLayout: NextPageWithLayout = () => <EdgeFunctionDetails />
@@ -11,7 +11,7 @@ import { useFlag } from 'common'
import ReportWidget from 'components/interfaces/Reports/ReportWidget'
import DefaultLayout from 'components/layouts/DefaultLayout'
import EdgeFunctionDetailsLayout from 'components/layouts/EdgeFunctionsLayout/EdgeFunctionDetailsLayout'
import { PageContainer } from 'components/ui/PageContainer'
import { PageContainer } from 'ui-patterns/PageContainer'
import AreaChart from 'components/ui/Charts/AreaChart'
import StackedBarChart from 'components/ui/Charts/StackedBarChart'
import NoPermission from 'components/ui/NoPermission'
@@ -30,7 +30,7 @@ import {
Button,
WarningIcon,
} from 'ui'
import { PageSection } from 'components/ui/PageSection'
import { PageSection } from 'ui-patterns/PageSection'
const CHART_INTERVALS: ChartIntervals[] = [
{
@@ -135,7 +135,7 @@ const PageLayout: NextPageWithLayout = () => {
return (
<PageContainer size="full">
<PageSection.Root>
<div className="flex flex-row items-center gap-2">
<div className="flex flex-row items-center gap-2 mb-4">
<div className="flex items-center">
{CHART_INTERVALS.map((item, i) => {
const classes = []
@@ -10,8 +10,8 @@ import {
} from 'components/interfaces/Functions/FunctionsEmptyState'
import DefaultLayout from 'components/layouts/DefaultLayout'
import EdgeFunctionsLayout from 'components/layouts/EdgeFunctionsLayout/EdgeFunctionsLayout'
import { PageContainer } from 'components/ui/PageContainer'
import { PageHeader } from 'components/ui/PageHeader'
import { PageContainer } from 'ui-patterns/PageContainer'
import { PageHeader } from 'ui-patterns/PageHeader'
import AlertError from 'components/ui/AlertError'
import { DocsButton } from 'components/ui/DocsButton'
import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
@@ -19,7 +19,7 @@ import { useEdgeFunctionsQuery } from 'data/edge-functions/edge-functions-query'
import { DOCS_URL, IS_PLATFORM } from 'lib/constants'
import type { NextPageWithLayout } from 'types'
import { Button, Card, Table, TableBody, TableHead, TableHeader, TableRow } from 'ui'
import { PageSection } from 'components/ui/PageSection'
import { PageSection } from 'ui-patterns/PageSection'
const EdgeFunctionsPage: NextPageWithLayout = () => {
const { ref } = useParams()
+3
View File
@@ -12,6 +12,9 @@ export * from './src/FilterBar'
export * from './src/GlassPanel'
export * from './src/InnerSideMenu'
export * from './src/McpUrlBuilder'
export * from './src/PageContainer'
export * from './src/PageHeader'
export * from './src/PageSection'
export * from './src/PopupFrame'
export * from './src/ShimmeringLoader'
export * from './src/TimestampInfo'
+12
View File
@@ -318,6 +318,18 @@
"import": "./src/McpUrlBuilder/index.ts",
"types": "./src/McpUrlBuilder/index.ts"
},
"./PageContainer": {
"import": "./src/PageContainer/index.tsx",
"types": "./src/PageContainer/index.tsx"
},
"./PageHeader": {
"import": "./src/PageHeader/index.tsx",
"types": "./src/PageHeader/index.tsx"
},
"./PageSection": {
"import": "./src/PageSection/index.tsx",
"types": "./src/PageSection/index.tsx"
},
"./MobileSheetNav/MobileSheetNav": {
"import": "./src/MobileSheetNav/MobileSheetNav.tsx",
"types": "./src/MobileSheetNav/MobileSheetNav.tsx"
@@ -51,3 +51,4 @@ export const PageContainer = forwardRef<HTMLDivElement, PageContainerProps>(
)
PageContainer.displayName = 'PageContainer'
@@ -217,3 +217,4 @@ export const PageSection = Object.assign(PageSectionRoot, {
Aside: PageSectionAside,
Content: PageSectionContent,
})
+3
View File
@@ -320,6 +320,9 @@ importers:
'@shikijs/compat':
specifier: ^1.1.7
version: 1.29.2
'@tailwindcss/container-queries':
specifier: ^0.1.1
version: 0.1.1(tailwindcss@3.4.1)
'@types/lodash.template':
specifier: 4.5.0
version: 4.5.0