From 2c48910673c19ef8e3e34c98a0a600c1de70e6ec Mon Sep 17 00:00:00 2001 From: "kemal.earth" <606977+kemaldotearth@users.noreply.github.com> Date: Thu, 12 Mar 2026 10:57:46 +0000 Subject: [PATCH] feat(design-system): status code component (#42064) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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. ## 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. ✏️ Tip: You can customize this high-level summary in your review settings. --- apps/design-system/__registry__/index.tsx | 22 ++++ apps/design-system/config/docs.ts | 5 + .../content/docs/fragments/status-codes.mdx | 35 ++++++ .../default/example/status-code-demo.tsx | 12 ++ apps/design-system/registry/examples.ts | 5 + apps/design-system/registry/fragments.ts | 6 + .../Auth/Overview/OverviewMetrics.tsx | 69 ++++-------- packages/ui-patterns/index.tsx | 1 + packages/ui-patterns/src/StatusCode/index.tsx | 103 ++++++++++++++++++ 9 files changed, 213 insertions(+), 45 deletions(-) create mode 100644 apps/design-system/content/docs/fragments/status-codes.mdx create mode 100644 apps/design-system/registry/default/example/status-code-demo.tsx create mode 100644 packages/ui-patterns/src/StatusCode/index.tsx diff --git a/apps/design-system/__registry__/index.tsx b/apps/design-system/__registry__/index.tsx index 412a1c26944..b6e9f301197 100644 --- a/apps/design-system/__registry__/index.tsx +++ b/apps/design-system/__registry__/index.tsx @@ -71,6 +71,17 @@ export const Index: Record = { 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 = { 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", diff --git a/apps/design-system/config/docs.ts b/apps/design-system/config/docs.ts index 33903e95e2b..ab73703af78 100644 --- a/apps/design-system/config/docs.ts +++ b/apps/design-system/config/docs.ts @@ -202,6 +202,11 @@ export const docsConfig: DocsConfig = { href: '/docs/fragments/confirmation-modal', items: [], }, + { + title: 'Status Codes', + href: '/docs/fragments/status-codes', + items: [], + }, ], }, { diff --git a/apps/design-system/content/docs/fragments/status-codes.mdx b/apps/design-system/content/docs/fragments/status-codes.mdx new file mode 100644 index 00000000000..e9f1402774d --- /dev/null +++ b/apps/design-system/content/docs/fragments/status-codes.mdx @@ -0,0 +1,35 @@ +--- +title: Status Code +description: Visually informative blocks for displaying HTTP status codes with method indicators. +fragment: true +--- + + + +## Usage + +```tsx +import { StatusCode } from 'ui-patterns' +``` + +```tsx + +``` + +## 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 | diff --git a/apps/design-system/registry/default/example/status-code-demo.tsx b/apps/design-system/registry/default/example/status-code-demo.tsx new file mode 100644 index 00000000000..4f41585f901 --- /dev/null +++ b/apps/design-system/registry/default/example/status-code-demo.tsx @@ -0,0 +1,12 @@ +import { StatusCode } from 'ui-patterns' + +export default function StatusCodeDemo() { + return ( +
+ + + + +
+ ) +} diff --git a/apps/design-system/registry/examples.ts b/apps/design-system/registry/examples.ts index 3a236c19096..821c5530446 100644 --- a/apps/design-system/registry/examples.ts +++ b/apps/design-system/registry/examples.ts @@ -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'], + }, ] diff --git a/apps/design-system/registry/fragments.ts b/apps/design-system/registry/fragments.ts index 3a71fbcdb47..1a389e4679a 100644 --- a/apps/design-system/registry/fragments.ts +++ b/apps/design-system/registry/fragments.ts @@ -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', + }, ] diff --git a/apps/studio/components/interfaces/Auth/Overview/OverviewMetrics.tsx b/apps/studio/components/interfaces/Auth/Overview/OverviewMetrics.tsx index d9619760498..a26ee94281e 100644 --- a/apps/studio/components/interfaces/Auth/Overview/OverviewMetrics.tsx +++ b/apps/studio/components/interfaces/Auth/Overview/OverviewMetrics.tsx @@ -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 ( -
- - - {row.method} - - - {row.status_code} - - -
- ) + return }, }, { diff --git a/packages/ui-patterns/index.tsx b/packages/ui-patterns/index.tsx index f7ffdde0b60..f0bcf7d138e 100644 --- a/packages/ui-patterns/index.tsx +++ b/packages/ui-patterns/index.tsx @@ -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' diff --git a/packages/ui-patterns/src/StatusCode/index.tsx b/packages/ui-patterns/src/StatusCode/index.tsx new file mode 100644 index 00000000000..54052bff441 --- /dev/null +++ b/packages/ui-patterns/src/StatusCode/index.tsx @@ -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 ( +
+ + {method && ( + + + {method} + + + )} + + + {statusCode} + + + +
+ ) +}