mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
feat(design-system): status code component (#42064)
## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Formalising our status code component that was introduced in the new charts + update auth overview page. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added a StatusCode UI component to display HTTP methods and status codes with color-coded indicators. * Added a demo showcasing multiple StatusCode examples and alignment variants. * **Documentation** * Added a documentation fragment and sidebar entry for Status Codes with a preview of the demo. * **Refactor** * Updated an Auth Overview view to use the new StatusCode component for consistent status rendering. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
1 parent
5b6a1c9858
commit
2c48910673
9 files changed
+213
-45
No files matched your search
@@ -71,6 +71,17 @@ export const Index: Record<string, any> = {
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"StatusCode": {
|
||||
name: "StatusCode",
|
||||
type: "components:fragment",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/../../packages/ui-patterns/src/StatusCode")),
|
||||
source: "",
|
||||
files: ["registry/default//StatusCode/index.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"accordion-demo": {
|
||||
name: "accordion-demo",
|
||||
type: "components:example",
|
||||
@@ -2931,6 +2942,17 @@ export const Index: Record<string, any> = {
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"status-code-demo": {
|
||||
name: "status-code-demo",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/status-code-demo")),
|
||||
source: "",
|
||||
files: ["registry/default/example/status-code-demo.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"chart-bar-interactive": {
|
||||
name: "chart-bar-interactive",
|
||||
type: "components:block",
|
||||
|
||||
@@ -202,6 +202,11 @@ export const docsConfig: DocsConfig = {
|
||||
href: '/docs/fragments/confirmation-modal',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Status Codes',
|
||||
href: '/docs/fragments/status-codes',
|
||||
items: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
title: Status Code
|
||||
description: Visually informative blocks for displaying HTTP status codes with method indicators.
|
||||
fragment: true
|
||||
---
|
||||
|
||||
<ComponentPreview name="status-code-demo" peekCode wide />
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import { StatusCode } from 'ui-patterns'
|
||||
```
|
||||
|
||||
```tsx
|
||||
<StatusCode method="GET" statusCode="200" />
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
| ------------ | --------------------------------- | -------- | ---------------------------------------------------------------------------- |
|
||||
| `statusCode` | `number \| string \| undefined` | — | The HTTP status code to display. Drives the color coding (see below). |
|
||||
| `method` | `string` | — | Optional HTTP method label (e.g. `GET`, `POST`). Rendered as a pill prefix. |
|
||||
| `className` | `string` | — | Additional CSS classes applied to the root element. |
|
||||
|
||||
### Color coding
|
||||
|
||||
The status code pill color is determined automatically from the `statusCode` value:
|
||||
|
||||
| Value | Color |
|
||||
| ---------------------------------- | ---------- |
|
||||
| `1xx`, `2xx`, `3xx`, `'info'`, `'success'`, `undefined` | Neutral |
|
||||
| `4xx`, `'warning'`, `'redirect'` | Warning |
|
||||
| `5xx`, `'error'` | Destructive |
|
||||
@@ -0,0 +1,12 @@
|
||||
import { StatusCode } from 'ui-patterns'
|
||||
|
||||
export default function StatusCodeDemo() {
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<StatusCode method="GET" statusCode="200" />
|
||||
<StatusCode method="POST" statusCode="404" />
|
||||
<StatusCode method="DELETE" statusCode="500" />
|
||||
<StatusCode method="PUT" statusCode="100" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1593,4 +1593,9 @@ export const examples: Registry = [
|
||||
registryDependencies: ['mermaid'],
|
||||
files: ['example/mermaid-basic.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'status-code-demo',
|
||||
type: 'components:example',
|
||||
files: ['example/status-code-demo.tsx'],
|
||||
},
|
||||
]
|
||||
@@ -37,4 +37,10 @@ export const fragments: Registry = [
|
||||
files: ['/PageSection/index.tsx'],
|
||||
optionalPath: '/PageSection',
|
||||
},
|
||||
{
|
||||
name: 'StatusCode',
|
||||
type: 'components:fragment',
|
||||
files: ['/StatusCode/index.tsx'],
|
||||
optionalPath: '/StatusCode',
|
||||
},
|
||||
]
|
||||
@@ -1,13 +1,28 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useParams } from 'common'
|
||||
import { SIDEBAR_KEYS } from 'components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
|
||||
import AlertError from 'components/ui/AlertError'
|
||||
import { getStatusColor } from 'components/ui/DataTable/DataTable.utils'
|
||||
import { Service } from 'data/graphql/graphql'
|
||||
import dayjs from 'dayjs'
|
||||
import { ChevronRight, ExternalLink, Telescope, BarChart2, Bot } from 'lucide-react'
|
||||
import { BarChart2, Bot, ChevronRight, ExternalLink, Telescope } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
import { useParams } from 'common'
|
||||
import { getStatusLevel } from 'components/interfaces/UnifiedLogs/UnifiedLogs.utils'
|
||||
import AlertError from 'components/ui/AlertError'
|
||||
import { cn, Tooltip, TooltipContent, TooltipTrigger, Button } from 'ui'
|
||||
import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state'
|
||||
import { useSidebarManagerSnapshot } from 'state/sidebar-manager-state'
|
||||
import { AiIconAnimation, Button, cn, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
|
||||
import { StatusCode } from 'ui-patterns'
|
||||
import {
|
||||
Chart,
|
||||
ChartActions,
|
||||
ChartCard,
|
||||
ChartContent,
|
||||
ChartEmptyState,
|
||||
ChartHeader,
|
||||
ChartLoadingState,
|
||||
ChartMetric,
|
||||
ChartTitle,
|
||||
} from 'ui-patterns/Chart'
|
||||
import {
|
||||
PageSection,
|
||||
PageSectionContent,
|
||||
@@ -15,17 +30,8 @@ import {
|
||||
PageSectionSummary,
|
||||
PageSectionTitle,
|
||||
} from 'ui-patterns/PageSection'
|
||||
import {
|
||||
Chart,
|
||||
ChartCard,
|
||||
ChartHeader,
|
||||
ChartActions,
|
||||
ChartMetric,
|
||||
ChartTitle,
|
||||
ChartContent,
|
||||
ChartEmptyState,
|
||||
ChartLoadingState,
|
||||
} from 'ui-patterns/Chart'
|
||||
|
||||
import { ErrorCodeTooltip } from '../../Settings/Logs/ErrorCodeTooltip'
|
||||
import {
|
||||
AuthErrorCodeRow,
|
||||
fetchTopAuthErrorCodes,
|
||||
@@ -40,13 +46,6 @@ import {
|
||||
getAuthSuccessRates,
|
||||
getMetricValues,
|
||||
} from './OverviewUsage.constants'
|
||||
import { getStatusColor } from 'components/ui/DataTable/DataTable.utils'
|
||||
import { ErrorCodeTooltip } from '../../Settings/Logs/ErrorCodeTooltip'
|
||||
import { Service } from 'data/graphql/graphql'
|
||||
import { AiIconAnimation } from 'ui'
|
||||
import { SIDEBAR_KEYS } from 'components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
|
||||
import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state'
|
||||
import { useSidebarManagerSnapshot } from 'state/sidebar-manager-state'
|
||||
|
||||
const StatCard = ({
|
||||
title,
|
||||
@@ -317,27 +316,7 @@ export const OverviewMetrics = ({ metrics, isLoading, error }: OverviewMetricsPr
|
||||
header: 'Request',
|
||||
className: 'w-auto !pr-0',
|
||||
render: (row) => {
|
||||
const level = getStatusLevel(row.status_code)
|
||||
const colors = getStatusColor(level)
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="flex-shrink-0 flex items-center text-xs font-mono">
|
||||
<span className="select-text py-0.5 px-2 text-center rounded-l rounded-r-none bg-surface-75 text-foreground-light border border-r-0">
|
||||
{row.method}
|
||||
</span>
|
||||
<span
|
||||
className={cn(
|
||||
'py-0.5 px-2 border rounded-l-0 rounded-r tabular-nums',
|
||||
colors.text,
|
||||
colors.bg,
|
||||
colors.border
|
||||
)}
|
||||
>
|
||||
{row.status_code}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
return <StatusCode method={row.method} statusCode={row.status_code} />
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -24,5 +24,6 @@ export * from './src/PopupFrame'
|
||||
export * from './src/PromoToast'
|
||||
export * from './src/Row'
|
||||
export * from './src/ShimmeringLoader'
|
||||
export * from './src/StatusCode'
|
||||
export * from './src/TimestampInfo'
|
||||
export * from './src/Toc'
|
||||
@@ -0,0 +1,103 @@
|
||||
'use client'
|
||||
|
||||
import { cn } from 'ui'
|
||||
|
||||
export interface StatusCodeProps {
|
||||
method?: string
|
||||
statusCode: number | string | undefined
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function getStatusColor(
|
||||
value?: number | string,
|
||||
method?: string
|
||||
): Record<'text' | 'bg' | 'border', string> {
|
||||
if (!method && value !== undefined) {
|
||||
const statusNum = Number(value)
|
||||
|
||||
const isValidHttpStatus = !isNaN(statusNum) && statusNum >= 100 && statusNum < 600
|
||||
|
||||
if (!isValidHttpStatus) {
|
||||
return {
|
||||
text: 'text-foreground-lighter',
|
||||
bg: 'bg-surface-200',
|
||||
border: '',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const normalized =
|
||||
typeof value === 'number'
|
||||
? value < 100
|
||||
? String(value)
|
||||
: String(Math.floor(value / 100))
|
||||
: typeof value === 'string' && /^\d+$/.test(value) && value.length >= 3
|
||||
? String(Math.floor(Number(value) / 100))
|
||||
: value
|
||||
|
||||
switch (normalized) {
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case 'info':
|
||||
case 'success':
|
||||
case undefined:
|
||||
return {
|
||||
text: 'text-foreground-lighter',
|
||||
bg: 'bg-surface-200',
|
||||
border: '',
|
||||
}
|
||||
case '4':
|
||||
case 'warning':
|
||||
case 'redirect':
|
||||
return {
|
||||
text: 'text-warning',
|
||||
bg: 'bg-warning-300',
|
||||
border: 'border-warning-500/50',
|
||||
}
|
||||
case '5':
|
||||
case 'error':
|
||||
return {
|
||||
text: 'text-destructive',
|
||||
bg: 'bg-destructive-300',
|
||||
border: 'border-destructive-500/50',
|
||||
}
|
||||
default:
|
||||
return {
|
||||
text: 'text-foreground-lighter',
|
||||
bg: 'bg-surface-200',
|
||||
border: '',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const StatusCode = ({ method, statusCode, className }: StatusCodeProps) => {
|
||||
const colors = getStatusColor(statusCode, method)
|
||||
|
||||
return (
|
||||
<div className={cn('flex items-center gap-2', className)}>
|
||||
<span className="flex-shrink-0 flex text-xs font-mono items-start justify-start">
|
||||
{method && (
|
||||
<span className="flex items-center justify-end">
|
||||
<span className="select-text py-0.5 px-2 text-right rounded-l rounded-r-none bg-surface-75 text-foreground-light border border-r-0 w-auto">
|
||||
{method}
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
<span className="flex items-center justify-start">
|
||||
<span
|
||||
className={cn(
|
||||
'py-0.5 px-2 border rounded-l-0 rounded-r tabular-nums text-left w-auto',
|
||||
!method && 'rounded-l',
|
||||
colors.text,
|
||||
colors.bg,
|
||||
colors.border
|
||||
)}
|
||||
>
|
||||
{statusCode}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user