mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
chore(design-system): generate and ignore registry output (#49290)
## What kind of change does this PR introduce? Chore. Stops committing generated design-system registry output ([DEPR-647](https://linear.app/supabase/issue/DEPR-647/generate-and-ignore-design-system-registry-output)). ## What is the current behavior? `apps/design-system/__registry__` is build output from `registry/`, but the chart snapshots and index are committed. That makes reviews noisy, and a forgotten `build:registry` leaves `master` out of date until someone else regenerates it. ## What is the new behavior? `pnpm dev` and `pnpm typecheck` generate `__registry__` automatically. The directory is gitignored, and the previously tracked snapshots are removed. `pnpm build` still generates it as before. ## To test - From the repo root, run `pnpm --filter=design-system generate:registry` and confirm `apps/design-system/__registry__/index.tsx` is created locally and is untracked. - Run `pnpm dev:design-system`, open [http://localhost:3003](http://localhost:3003), and open any component docs page with a live preview (for example Charts). Previews and source panels should still load. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Clarified when the component registry is generated and how to regenerate it. * Expanded component documentation guidance, including content sources and generated-file editing restrictions. * **Chores** * Improved registry generation across development, type checking, builds, and cleanup. * Generated registry files are now excluded from version control and linting. * Improved reliability when creating and refreshing generated registry files. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
1 parent
9ae6e54dd5
commit
facc2df09e
13 files changed
+39
-4636
No files matched your search
@@ -35,4 +35,7 @@ yarn-error.log*
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
.contentlayer
|
||||
.contentlayer
|
||||
|
||||
# Generated by `pnpm generate:registry` (`scripts/build-registry.mts`)
|
||||
/__registry__
|
||||
@@ -12,13 +12,15 @@ pnpm i
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
The `dev` command runs both the Next.js development server and Contentlayer concurrently, which is recommended for most development workflows.
|
||||
The `dev` command generates `__registry__`, then runs the Next.js development server and Contentlayer together. That is the recommended workflow.
|
||||
|
||||
### Alternative commands
|
||||
|
||||
You can also run the development server and content watcher separately:
|
||||
You can also run the development server and content watcher separately. Generate the registry first, because `dev:next` and `dev:content` do not:
|
||||
|
||||
```bash
|
||||
pnpm generate:registry
|
||||
|
||||
# Run only the Next.js development server
|
||||
pnpm dev:next
|
||||
|
||||
@@ -26,19 +28,11 @@ pnpm dev:next
|
||||
pnpm dev:content
|
||||
```
|
||||
|
||||
Or run the development server from the root directory:
|
||||
From the repo root, `pnpm dev:design-system` runs the same `dev` script, so it also generates `__registry__`. If you split the watchers from the root, generate first:
|
||||
|
||||
```bash
|
||||
pnpm dev:design-system
|
||||
```
|
||||
|
||||
To run both the development server and content watcher from the root directory, you can use:
|
||||
|
||||
```bash
|
||||
# Run the development server
|
||||
pnpm dev:design-system
|
||||
|
||||
# Run the content watcher (in a separate terminal shell)
|
||||
pnpm --filter=design-system generate:registry
|
||||
pnpm --filter=design-system dev:next
|
||||
pnpm --filter=design-system dev:content
|
||||
```
|
||||
|
||||
@@ -46,27 +40,29 @@ Open [http://localhost:3003](http://localhost:3003) in your browser to see the r
|
||||
|
||||
### Watching for MDX changes
|
||||
|
||||
The `dev` command automatically watches for changes to MDX files with hot reload. If you're running the `pnpm dev:next` separately, you'll need to run `pnpm dev:content` in a separate terminal shell to watch for content changes.
|
||||
The `dev` command watches MDX files and hot-reloads them. If you are running `pnpm dev:next` on its own, also run `pnpm dev:content` in another terminal.
|
||||
|
||||
### Adding components
|
||||
|
||||
The design system _references_ components rather than housing them. That’s an important distinction to make, as everything that follows here is about the documentation of components. You can add or edit components in one of these two places:
|
||||
The design system _references_ components rather than housing them. That distinction matters: everything below is about documenting components, not implementing them. Add or edit the components themselves in one of these two places:
|
||||
|
||||
- [`packages/ui`](https://github.com/supabase/supabase/tree/master/packages/ui): basic UI components
|
||||
- [`packages/ui-patterns`](https://github.com/supabase/supabase/tree/master/packages/ui-patterns): components which are built using NPM libraries or amalgamations of components from `patterns/ui`
|
||||
- [`packages/ui-patterns`](https://github.com/supabase/supabase/tree/master/packages/ui-patterns): components built from libraries or from `packages/ui`
|
||||
|
||||
There are several parts of this design system that need to be manually updated after components have been added or removed (from documentation). These include:
|
||||
After you add or remove documented components, update these source files:
|
||||
|
||||
- `config/docs.ts`: list of components in the sidebar
|
||||
- `content/docs`: the actual component documentation
|
||||
- `registry/examples.ts`: list of example components
|
||||
- `registry/fragments.ts`: list of fragment components
|
||||
- `registry/charts.ts`: list of chart components
|
||||
- `registry/default/example/*`: the actual example components
|
||||
- `content/docs`: the component documentation
|
||||
- `registry/examples.ts`: example components
|
||||
- `registry/fragments.ts`: fragment components
|
||||
- `registry/charts.ts`: chart components
|
||||
- `registry/copy-writing.ts`: copywriting examples
|
||||
- `registry/default/example/*`: the example component implementations
|
||||
- `registry/default/block/*`: chart block implementations, when you add a chart
|
||||
|
||||
You will need to rebuild the design system’s registry after making new additions:
|
||||
Do not edit `__registry__`. `pnpm dev`, `pnpm typecheck`, and `pnpm build` generate it from the files above, and it is gitignored. If you add registry entries while the app is already running, regenerate it:
|
||||
|
||||
```bash
|
||||
cd apps/design-system
|
||||
pnpm build:registry
|
||||
pnpm generate:registry
|
||||
```
|
||||
@@ -1,211 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import { Bar, BarChart, CartesianGrid, XAxis } from 'recharts'
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
ChartConfig,
|
||||
ChartContainer,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
} from 'ui'
|
||||
|
||||
export const description = 'An interactive bar chart'
|
||||
|
||||
const chartData = [
|
||||
{ date: '2024-04-01', desktop: 222, mobile: 150 },
|
||||
{ date: '2024-04-02', desktop: 97, mobile: 180 },
|
||||
{ date: '2024-04-03', desktop: 167, mobile: 120 },
|
||||
{ date: '2024-04-04', desktop: 242, mobile: 260 },
|
||||
{ date: '2024-04-05', desktop: 373, mobile: 290 },
|
||||
{ date: '2024-04-06', desktop: 301, mobile: 340 },
|
||||
{ date: '2024-04-07', desktop: 245, mobile: 180 },
|
||||
{ date: '2024-04-08', desktop: 409, mobile: 320 },
|
||||
{ date: '2024-04-09', desktop: 59, mobile: 110 },
|
||||
{ date: '2024-04-10', desktop: 261, mobile: 190 },
|
||||
{ date: '2024-04-11', desktop: 327, mobile: 350 },
|
||||
{ date: '2024-04-12', desktop: 292, mobile: 210 },
|
||||
{ date: '2024-04-13', desktop: 342, mobile: 380 },
|
||||
{ date: '2024-04-14', desktop: 137, mobile: 220 },
|
||||
{ date: '2024-04-15', desktop: 120, mobile: 170 },
|
||||
{ date: '2024-04-16', desktop: 138, mobile: 190 },
|
||||
{ date: '2024-04-17', desktop: 446, mobile: 360 },
|
||||
{ date: '2024-04-18', desktop: 364, mobile: 410 },
|
||||
{ date: '2024-04-19', desktop: 243, mobile: 180 },
|
||||
{ date: '2024-04-20', desktop: 89, mobile: 150 },
|
||||
{ date: '2024-04-21', desktop: 137, mobile: 200 },
|
||||
{ date: '2024-04-22', desktop: 224, mobile: 170 },
|
||||
{ date: '2024-04-23', desktop: 138, mobile: 230 },
|
||||
{ date: '2024-04-24', desktop: 387, mobile: 290 },
|
||||
{ date: '2024-04-25', desktop: 215, mobile: 250 },
|
||||
{ date: '2024-04-26', desktop: 75, mobile: 130 },
|
||||
{ date: '2024-04-27', desktop: 383, mobile: 420 },
|
||||
{ date: '2024-04-28', desktop: 122, mobile: 180 },
|
||||
{ date: '2024-04-29', desktop: 315, mobile: 240 },
|
||||
{ date: '2024-04-30', desktop: 454, mobile: 380 },
|
||||
{ date: '2024-05-01', desktop: 165, mobile: 220 },
|
||||
{ date: '2024-05-02', desktop: 293, mobile: 310 },
|
||||
{ date: '2024-05-03', desktop: 247, mobile: 190 },
|
||||
{ date: '2024-05-04', desktop: 385, mobile: 420 },
|
||||
{ date: '2024-05-05', desktop: 481, mobile: 390 },
|
||||
{ date: '2024-05-06', desktop: 498, mobile: 520 },
|
||||
{ date: '2024-05-07', desktop: 388, mobile: 300 },
|
||||
{ date: '2024-05-08', desktop: 149, mobile: 210 },
|
||||
{ date: '2024-05-09', desktop: 227, mobile: 180 },
|
||||
{ date: '2024-05-10', desktop: 293, mobile: 330 },
|
||||
{ date: '2024-05-11', desktop: 335, mobile: 270 },
|
||||
{ date: '2024-05-12', desktop: 197, mobile: 240 },
|
||||
{ date: '2024-05-13', desktop: 197, mobile: 160 },
|
||||
{ date: '2024-05-14', desktop: 448, mobile: 490 },
|
||||
{ date: '2024-05-15', desktop: 473, mobile: 380 },
|
||||
{ date: '2024-05-16', desktop: 338, mobile: 400 },
|
||||
{ date: '2024-05-17', desktop: 499, mobile: 420 },
|
||||
{ date: '2024-05-18', desktop: 315, mobile: 350 },
|
||||
{ date: '2024-05-19', desktop: 235, mobile: 180 },
|
||||
{ date: '2024-05-20', desktop: 177, mobile: 230 },
|
||||
{ date: '2024-05-21', desktop: 82, mobile: 140 },
|
||||
{ date: '2024-05-22', desktop: 81, mobile: 120 },
|
||||
{ date: '2024-05-23', desktop: 252, mobile: 290 },
|
||||
{ date: '2024-05-24', desktop: 294, mobile: 220 },
|
||||
{ date: '2024-05-25', desktop: 201, mobile: 250 },
|
||||
{ date: '2024-05-26', desktop: 213, mobile: 170 },
|
||||
{ date: '2024-05-27', desktop: 420, mobile: 460 },
|
||||
{ date: '2024-05-28', desktop: 233, mobile: 190 },
|
||||
{ date: '2024-05-29', desktop: 78, mobile: 130 },
|
||||
{ date: '2024-05-30', desktop: 340, mobile: 280 },
|
||||
{ date: '2024-05-31', desktop: 178, mobile: 230 },
|
||||
{ date: '2024-06-01', desktop: 178, mobile: 200 },
|
||||
{ date: '2024-06-02', desktop: 470, mobile: 410 },
|
||||
{ date: '2024-06-03', desktop: 103, mobile: 160 },
|
||||
{ date: '2024-06-04', desktop: 439, mobile: 380 },
|
||||
{ date: '2024-06-05', desktop: 88, mobile: 140 },
|
||||
{ date: '2024-06-06', desktop: 294, mobile: 250 },
|
||||
{ date: '2024-06-07', desktop: 323, mobile: 370 },
|
||||
{ date: '2024-06-08', desktop: 385, mobile: 320 },
|
||||
{ date: '2024-06-09', desktop: 438, mobile: 480 },
|
||||
{ date: '2024-06-10', desktop: 155, mobile: 200 },
|
||||
{ date: '2024-06-11', desktop: 92, mobile: 150 },
|
||||
{ date: '2024-06-12', desktop: 492, mobile: 420 },
|
||||
{ date: '2024-06-13', desktop: 81, mobile: 130 },
|
||||
{ date: '2024-06-14', desktop: 426, mobile: 380 },
|
||||
{ date: '2024-06-15', desktop: 307, mobile: 350 },
|
||||
{ date: '2024-06-16', desktop: 371, mobile: 310 },
|
||||
{ date: '2024-06-17', desktop: 475, mobile: 520 },
|
||||
{ date: '2024-06-18', desktop: 107, mobile: 170 },
|
||||
{ date: '2024-06-19', desktop: 341, mobile: 290 },
|
||||
{ date: '2024-06-20', desktop: 408, mobile: 450 },
|
||||
{ date: '2024-06-21', desktop: 169, mobile: 210 },
|
||||
{ date: '2024-06-22', desktop: 317, mobile: 270 },
|
||||
{ date: '2024-06-23', desktop: 480, mobile: 530 },
|
||||
{ date: '2024-06-24', desktop: 132, mobile: 180 },
|
||||
{ date: '2024-06-25', desktop: 141, mobile: 190 },
|
||||
{ date: '2024-06-26', desktop: 434, mobile: 380 },
|
||||
{ date: '2024-06-27', desktop: 448, mobile: 490 },
|
||||
{ date: '2024-06-28', desktop: 149, mobile: 200 },
|
||||
{ date: '2024-06-29', desktop: 103, mobile: 160 },
|
||||
{ date: '2024-06-30', desktop: 446, mobile: 400 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
views: {
|
||||
label: 'Page Views',
|
||||
},
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
mobile: {
|
||||
label: 'Mobile',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
const [activeChart, setActiveChart] = React.useState<keyof typeof chartConfig>('desktop')
|
||||
|
||||
const total = React.useMemo(
|
||||
() => ({
|
||||
desktop: chartData.reduce((acc, curr) => acc + curr.desktop, 0),
|
||||
mobile: chartData.reduce((acc, curr) => acc + curr.mobile, 0),
|
||||
}),
|
||||
[]
|
||||
)
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="flex flex-col items-stretch space-y-0 border-b p-0 sm:flex-row">
|
||||
<div className="flex flex-1 flex-col justify-center gap-1 px-6 py-5 sm:py-6">
|
||||
<CardTitle>Bar Chart - Interactive</CardTitle>
|
||||
<CardDescription>Showing total visitors for the last 3 months</CardDescription>
|
||||
</div>
|
||||
<div className="flex">
|
||||
{['desktop', 'mobile'].map((key) => {
|
||||
const chart = key as keyof typeof chartConfig
|
||||
return (
|
||||
<button
|
||||
tabIndex={0}
|
||||
key={chart}
|
||||
data-active={activeChart === chart}
|
||||
className="relative z-30 flex flex-1 flex-col justify-center gap-1 border-t px-6 py-4 text-left even:border-l data-[active=true]:bg-surface-100 sm:border-l sm:border-t-0 sm:px-8 sm:py-6"
|
||||
onClick={() => setActiveChart(chart)}
|
||||
>
|
||||
<span className="text-xs text-foreground-lighter">{chartConfig[chart].label}</span>
|
||||
<span className="text-lg font-bold leading-none sm:text-3xl">
|
||||
{total[key as keyof typeof total].toLocaleString()}
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="px-2 sm:p-6">
|
||||
<ChartContainer config={chartConfig} className="aspect-auto h-[250px] w-full">
|
||||
<BarChart
|
||||
accessibilityLayer
|
||||
data={chartData}
|
||||
margin={{
|
||||
left: 12,
|
||||
right: 12,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="date"
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
minTickGap={32}
|
||||
tickFormatter={(value) => {
|
||||
const date = new Date(value)
|
||||
return date.toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<ChartTooltip
|
||||
content={
|
||||
<ChartTooltipContent
|
||||
className="w-[150px]"
|
||||
nameKey="views"
|
||||
labelFormatter={(value) => {
|
||||
return new Date(value).toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
})
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Bar dataKey={activeChart} fill={`var(--color-${activeChart})`} />
|
||||
</BarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { BarChart2, ExternalLink, LineChart } from 'lucide-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import {
|
||||
Chart,
|
||||
ChartActions,
|
||||
ChartBar,
|
||||
ChartCard,
|
||||
ChartContent,
|
||||
ChartEmptyState,
|
||||
ChartHeader,
|
||||
ChartLine,
|
||||
ChartLoadingState,
|
||||
ChartTitle,
|
||||
} from 'ui-patterns/Chart'
|
||||
|
||||
export default function ChartComposedActions() {
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [chartType, setChartType] = useState<'bar' | 'line'>('bar')
|
||||
|
||||
const actions = [
|
||||
{
|
||||
label: 'Open in Logs Explorer',
|
||||
onClick: () => {
|
||||
alert('Opening in Logs Explorer...')
|
||||
},
|
||||
icon: <ExternalLink size={12} />,
|
||||
},
|
||||
{
|
||||
label: 'Toggle Chart Type',
|
||||
onClick: () => {
|
||||
setChartType(chartType === 'bar' ? 'line' : 'bar')
|
||||
},
|
||||
icon: chartType === 'bar' ? <LineChart size={12} /> : <BarChart2 size={12} />,
|
||||
},
|
||||
]
|
||||
|
||||
const data = Array.from({ length: 46 }, (_, i) => {
|
||||
const date = new Date()
|
||||
date.setMinutes(date.getMinutes() - i * 5) // Each point 5 minutes apart
|
||||
|
||||
return {
|
||||
timestamp: date.toISOString(),
|
||||
requests: Math.floor(Math.random() * 100),
|
||||
}
|
||||
}).reverse()
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setIsLoading(false)
|
||||
}, 1000)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6 w-8/12">
|
||||
<Chart isLoading={isLoading}>
|
||||
<ChartCard>
|
||||
<ChartHeader>
|
||||
<ChartTitle tooltip="This is a tooltip">Total Requests</ChartTitle>
|
||||
<ChartActions actions={actions} />
|
||||
</ChartHeader>
|
||||
<ChartContent
|
||||
isEmpty={data.length === 0}
|
||||
emptyState={
|
||||
<ChartEmptyState
|
||||
icon={<BarChart2 size={16} />}
|
||||
title="No data to show"
|
||||
description="It may take up to 24 hours for data to refresh"
|
||||
/>
|
||||
}
|
||||
loadingState={<ChartLoadingState />}
|
||||
>
|
||||
<div className="h-40">
|
||||
{chartType === 'bar' ? (
|
||||
<ChartBar
|
||||
data={data}
|
||||
dataKey="requests"
|
||||
showGrid={false}
|
||||
showYAxis={true}
|
||||
YAxisProps={{
|
||||
tickFormatter: (value) => `${value}k`,
|
||||
width: 80,
|
||||
}}
|
||||
isFullHeight={true}
|
||||
/>
|
||||
) : (
|
||||
<ChartLine
|
||||
data={data}
|
||||
dataKey="requests"
|
||||
showGrid={true}
|
||||
showYAxis={true}
|
||||
YAxisProps={{
|
||||
tickFormatter: (value) => `${value}k`,
|
||||
width: 80,
|
||||
}}
|
||||
isFullHeight={true}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</ChartContent>
|
||||
</ChartCard>
|
||||
</Chart>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { BarChart2, ExternalLink } from 'lucide-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import {
|
||||
Chart,
|
||||
ChartActions,
|
||||
ChartBar,
|
||||
ChartCard,
|
||||
ChartContent,
|
||||
ChartEmptyState,
|
||||
ChartHeader,
|
||||
ChartLine,
|
||||
ChartLoadingState,
|
||||
ChartTitle,
|
||||
} from 'ui-patterns/Chart'
|
||||
|
||||
export default function ComposedChartBasic() {
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
|
||||
const actions = [
|
||||
{
|
||||
label: 'Open in Logs Explorer',
|
||||
onClick: () => {
|
||||
alert('Ta da! You clicked me! 🎉')
|
||||
},
|
||||
icon: <ExternalLink size={12} />,
|
||||
},
|
||||
]
|
||||
|
||||
const data = Array.from({ length: 40 }, (_, i) => {
|
||||
const date = new Date()
|
||||
date.setMinutes(date.getMinutes() - i * 3) // Each point 3 minutes apart
|
||||
|
||||
const progress = i / 40
|
||||
const standard_score = Math.floor(55 + progress * 55 + (Math.random() - 0.5) * 12)
|
||||
const performance = Math.floor(35 + progress * 35 + (Math.random() - 0.5) * 10)
|
||||
const efficiency = Math.floor(25 + progress * 25 + (Math.random() - 0.5) * 12)
|
||||
|
||||
return {
|
||||
timestamp: date.toISOString(),
|
||||
standard_score: Math.max(0, Math.min(100, standard_score)),
|
||||
performance: Math.max(0, Math.min(100, performance)),
|
||||
efficiency: Math.max(0, Math.min(100, efficiency)),
|
||||
}
|
||||
}).reverse()
|
||||
|
||||
const chartConfig = {
|
||||
standard_score: {
|
||||
label: 'Standard Score',
|
||||
color: 'hsl(var(--brand-default))',
|
||||
},
|
||||
performance: {
|
||||
label: 'Performance',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
efficiency: {
|
||||
label: 'Efficiency',
|
||||
color: 'hsl(var(--chart-5))',
|
||||
},
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setIsLoading(false)
|
||||
}, 1000)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6 w-8/12">
|
||||
<Chart isLoading={isLoading}>
|
||||
<ChartCard>
|
||||
<ChartHeader>
|
||||
<ChartTitle tooltip="This is a tooltip">Standard Bar Chart</ChartTitle>
|
||||
<ChartActions actions={actions} />
|
||||
</ChartHeader>
|
||||
<ChartContent
|
||||
isEmpty={data.length === 0}
|
||||
emptyState={
|
||||
<ChartEmptyState
|
||||
icon={<BarChart2 size={16} />}
|
||||
title="No data to show"
|
||||
description="It may take up to 24 hours for data to refresh"
|
||||
/>
|
||||
}
|
||||
loadingState={<ChartLoadingState />}
|
||||
>
|
||||
<div className="h-40">
|
||||
<ChartBar
|
||||
data={data}
|
||||
dataKey="standard_score"
|
||||
showGrid={false}
|
||||
showYAxis={true}
|
||||
YAxisProps={{
|
||||
tickFormatter: (value) => `${value}k`,
|
||||
width: 80,
|
||||
}}
|
||||
isFullHeight={true}
|
||||
/>
|
||||
</div>
|
||||
</ChartContent>
|
||||
</ChartCard>
|
||||
</Chart>
|
||||
|
||||
<Chart isLoading={isLoading}>
|
||||
<ChartCard>
|
||||
<ChartHeader>
|
||||
<ChartTitle tooltip="This is a tooltip">Standard Line Chart</ChartTitle>
|
||||
<ChartActions actions={actions} />
|
||||
</ChartHeader>
|
||||
<ChartContent
|
||||
isEmpty={data.length === 0}
|
||||
emptyState={
|
||||
<ChartEmptyState
|
||||
icon={<BarChart2 size={16} />}
|
||||
title="No data to show"
|
||||
description="It may take up to 24 hours for data to refresh"
|
||||
/>
|
||||
}
|
||||
loadingState={<ChartLoadingState />}
|
||||
>
|
||||
<div className="h-40">
|
||||
<ChartLine
|
||||
data={data}
|
||||
dataKey="standard_score"
|
||||
dataKeys={['standard_score', 'performance', 'efficiency']}
|
||||
config={chartConfig}
|
||||
showGrid={true}
|
||||
showYAxis={true}
|
||||
YAxisProps={{
|
||||
tickFormatter: (value) => `${value}k`,
|
||||
width: 80,
|
||||
}}
|
||||
isFullHeight={true}
|
||||
/>
|
||||
</div>
|
||||
</ChartContent>
|
||||
</ChartCard>
|
||||
</Chart>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { BarChart2 } from 'lucide-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import {
|
||||
Chart,
|
||||
ChartCard,
|
||||
ChartContent,
|
||||
ChartEmptyState,
|
||||
ChartHeader,
|
||||
ChartLoadingState,
|
||||
ChartMetric,
|
||||
} from 'ui-patterns/Chart'
|
||||
import { LogsBarChart } from 'ui-patterns/LogsBarChart'
|
||||
|
||||
export default function ComposedChartDemo() {
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
|
||||
const data = Array.from({ length: 60 }, (_, i) => {
|
||||
const date = new Date()
|
||||
date.setMinutes(date.getMinutes() - i * 5) // Each point 5 minutes apart
|
||||
|
||||
return {
|
||||
timestamp: date.toISOString(),
|
||||
ok_count: Math.floor(Math.random() * 100), // Random value 0-99
|
||||
error_count: Math.floor(Math.random() * 50), // Random value 0-50
|
||||
warning_count: Math.floor(Math.random() * 50), // Random value 0-50
|
||||
}
|
||||
}).reverse()
|
||||
|
||||
const totalUsersValue = 4663
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setIsLoading(false)
|
||||
}, 1000)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="w-8/12">
|
||||
<Chart isLoading={isLoading}>
|
||||
<ChartCard>
|
||||
<ChartHeader>
|
||||
<ChartMetric
|
||||
label="Total Users"
|
||||
value={totalUsersValue.toLocaleString('en-US')}
|
||||
tooltip="This is a tooltip"
|
||||
/>
|
||||
<div className="flex items-center gap-6">
|
||||
<ChartMetric label="Warn" value="1.2k" status="warning" align="end" />
|
||||
<ChartMetric label="Err" value="736" status="negative" align="end" />
|
||||
</div>
|
||||
</ChartHeader>
|
||||
<ChartContent
|
||||
isEmpty={data.length === 0}
|
||||
emptyState={
|
||||
<ChartEmptyState
|
||||
icon={<BarChart2 size={16} />}
|
||||
title="No data to show"
|
||||
description="It may take up to 24 hours for data to refresh"
|
||||
/>
|
||||
}
|
||||
loadingState={<ChartLoadingState />}
|
||||
>
|
||||
<div className="h-40">
|
||||
<LogsBarChart data={data} isFullHeight={true} />
|
||||
</div>
|
||||
</ChartContent>
|
||||
</ChartCard>
|
||||
</Chart>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { ExternalLink } from 'lucide-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Skeleton } from 'ui'
|
||||
import {
|
||||
Chart,
|
||||
ChartActions,
|
||||
ChartCard,
|
||||
ChartContent,
|
||||
ChartHeader,
|
||||
ChartMetric,
|
||||
ChartSparkline,
|
||||
} from 'ui-patterns/Chart'
|
||||
|
||||
export default function ChartComposedMetrics() {
|
||||
const [data, setData] = useState<Array<{ value: number; timestamp: string }>>([])
|
||||
|
||||
const actions = [
|
||||
{
|
||||
label: 'Open in Logs Explorer',
|
||||
onClick: () => {
|
||||
alert('Ta da! You clicked me! 🎉')
|
||||
},
|
||||
icon: <ExternalLink size={12} />,
|
||||
},
|
||||
]
|
||||
|
||||
useEffect(() => {
|
||||
const now = new Date()
|
||||
setData(
|
||||
Array.from({ length: 12 }, (_, i) => ({
|
||||
value: Math.floor(4000 + i * 100 + (Math.random() * 2000 - 800)),
|
||||
timestamp: new Date(now.getTime() - (11 - i) * 60 * 60 * 1000).toISOString(),
|
||||
}))
|
||||
)
|
||||
}, [])
|
||||
|
||||
const averageValue = data.reduce((acc, curr) => acc + curr.value, 0) / data.length
|
||||
|
||||
const diff = data[data.length - 1]?.value - data[0]?.value || 0
|
||||
const diffPercentage = (diff / averageValue) * 100
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-2 gap-6 w-full">
|
||||
<Chart isLoading={!data.length}>
|
||||
<ChartCard>
|
||||
<ChartHeader className="pb-4" align="start">
|
||||
<ChartMetric label="Slow Queries" value={0} />
|
||||
<ChartActions actions={actions} />
|
||||
</ChartHeader>
|
||||
</ChartCard>
|
||||
</Chart>
|
||||
<Chart isLoading={!data.length}>
|
||||
<ChartCard>
|
||||
<ChartHeader className="pb-4" align="start">
|
||||
<ChartMetric label="Sign Ups" value={127} />
|
||||
<ChartActions actions={actions} />
|
||||
</ChartHeader>
|
||||
</ChartCard>
|
||||
</Chart>
|
||||
<Chart isLoading={!data.length}>
|
||||
<ChartCard>
|
||||
<ChartHeader align="start">
|
||||
<ChartMetric
|
||||
label="Active Users"
|
||||
value={averageValue.toLocaleString(undefined, { maximumFractionDigits: 0 })}
|
||||
diffValue={diffPercentage.toFixed(2) + '%'}
|
||||
/>
|
||||
<ChartActions actions={actions} />
|
||||
</ChartHeader>
|
||||
<ChartContent
|
||||
className="p-0"
|
||||
loadingState={<Skeleton className="w-full h-24 rounded-none mt-4" />}
|
||||
>
|
||||
<ChartSparkline data={data} dataKey="value" />
|
||||
</ChartContent>
|
||||
</ChartCard>
|
||||
</Chart>
|
||||
<Chart isLoading={!data.length}>
|
||||
<ChartCard>
|
||||
<ChartHeader align="start">
|
||||
<ChartMetric
|
||||
label="Active Users"
|
||||
value={averageValue.toLocaleString(undefined, { maximumFractionDigits: 0 })}
|
||||
diffValue={diffPercentage.toFixed(2) + '%'}
|
||||
/>
|
||||
<ChartActions actions={actions} />
|
||||
</ChartHeader>
|
||||
<ChartContent
|
||||
className="p-0"
|
||||
loadingState={<Skeleton className="w-full h-24 rounded-none mt-4" />}
|
||||
>
|
||||
<ChartSparkline data={data} dataKey="value" />
|
||||
</ChartContent>
|
||||
</ChartCard>
|
||||
</Chart>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { BarChart2, ExternalLink } from 'lucide-react'
|
||||
import { Badge, CriticalIcon } from 'ui'
|
||||
import {
|
||||
Chart,
|
||||
ChartActions,
|
||||
ChartCard,
|
||||
ChartContent,
|
||||
ChartDisabledState,
|
||||
ChartEmptyState,
|
||||
ChartHeader,
|
||||
ChartLoadingState,
|
||||
ChartTitle,
|
||||
} from 'ui-patterns/Chart'
|
||||
|
||||
export default function ChartComposedStates() {
|
||||
const actions = [
|
||||
{
|
||||
label: 'Open in Logs Explorer',
|
||||
onClick: () => {
|
||||
alert('Ta da! You clicked me! 🎉')
|
||||
},
|
||||
icon: <ExternalLink size={12} />,
|
||||
},
|
||||
]
|
||||
|
||||
const disabledActions = [
|
||||
{
|
||||
label: 'Upgrade to Pro',
|
||||
href: '#',
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6 w-8/12">
|
||||
<Chart isLoading={true}>
|
||||
<ChartCard>
|
||||
<ChartHeader>
|
||||
<ChartTitle tooltip="This is a tooltip">Response Errors</ChartTitle>
|
||||
<ChartActions actions={actions} />
|
||||
</ChartHeader>
|
||||
<ChartContent loadingState={<ChartLoadingState />}>My chart here...</ChartContent>
|
||||
</ChartCard>
|
||||
</Chart>
|
||||
|
||||
<Chart isErrored={true}>
|
||||
<ChartCard>
|
||||
<ChartHeader>
|
||||
<ChartTitle tooltip="This is a tooltip">Response Errors</ChartTitle>
|
||||
<ChartActions actions={actions} />
|
||||
</ChartHeader>
|
||||
<ChartContent
|
||||
isEmpty={true}
|
||||
errorState={
|
||||
<ChartEmptyState
|
||||
icon={<CriticalIcon />}
|
||||
title="Some error happened"
|
||||
description="Error happened why trying to fetch data. Please try again later."
|
||||
/>
|
||||
}
|
||||
emptyState={
|
||||
<ChartEmptyState
|
||||
icon={<BarChart2 size={20} />}
|
||||
title="No data to show"
|
||||
description="It may take up to 24 hours for data to refresh"
|
||||
/>
|
||||
}
|
||||
loadingState={<ChartLoadingState />}
|
||||
>
|
||||
My chart here...
|
||||
</ChartContent>
|
||||
</ChartCard>
|
||||
</Chart>
|
||||
|
||||
<Chart>
|
||||
<ChartCard>
|
||||
<ChartHeader>
|
||||
<ChartTitle tooltip="This is a tooltip">Response Errors</ChartTitle>
|
||||
<ChartActions actions={actions} />
|
||||
</ChartHeader>
|
||||
<ChartContent
|
||||
isEmpty={true}
|
||||
emptyState={
|
||||
<ChartEmptyState
|
||||
icon={<BarChart2 size={20} />}
|
||||
title="No data to show"
|
||||
description="It may take up to 24 hours for data to refresh"
|
||||
/>
|
||||
}
|
||||
loadingState={<ChartLoadingState />}
|
||||
>
|
||||
My chart here...
|
||||
</ChartContent>
|
||||
</ChartCard>
|
||||
</Chart>
|
||||
|
||||
<Chart isDisabled={true}>
|
||||
<ChartCard>
|
||||
<ChartHeader>
|
||||
<ChartTitle tooltip="This is a tooltip">Response Errors</ChartTitle>
|
||||
<ChartActions actions={actions} />
|
||||
</ChartHeader>
|
||||
<ChartContent
|
||||
loadingState={<ChartLoadingState />}
|
||||
disabledState={
|
||||
<ChartDisabledState
|
||||
icon={<Badge variant="success">Pro</Badge>}
|
||||
label="API Processing Time"
|
||||
description="This chart is available on the Pro plan and above"
|
||||
actions={disabledActions}
|
||||
/>
|
||||
}
|
||||
>
|
||||
My chart here...
|
||||
</ChartContent>
|
||||
</ChartCard>
|
||||
</Chart>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { format } from 'date-fns'
|
||||
import { BarChart2, ExternalLink } from 'lucide-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from 'ui'
|
||||
import {
|
||||
Chart,
|
||||
ChartActions,
|
||||
ChartBar,
|
||||
ChartCard,
|
||||
ChartContent,
|
||||
ChartEmptyState,
|
||||
ChartFooter,
|
||||
ChartHeader,
|
||||
ChartLoadingState,
|
||||
ChartTitle,
|
||||
} from 'ui-patterns/Chart'
|
||||
|
||||
export default function ChartComposedTable() {
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
|
||||
const actions = [
|
||||
{
|
||||
label: 'Open in Logs Explorer',
|
||||
onClick: () => {
|
||||
alert('Opening in Logs Explorer...')
|
||||
},
|
||||
icon: <ExternalLink size={12} />,
|
||||
},
|
||||
]
|
||||
|
||||
const data = Array.from({ length: 46 }, (_, i) => {
|
||||
const date = new Date()
|
||||
date.setMinutes(date.getMinutes() - i * 5) // Each point 5 minutes apart
|
||||
|
||||
return {
|
||||
timestamp: date.toISOString(),
|
||||
standard_score: Math.floor(Math.random() * 100),
|
||||
}
|
||||
}).reverse()
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setIsLoading(false)
|
||||
}, 1000)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6 w-8/12">
|
||||
<Chart isLoading={isLoading}>
|
||||
<ChartCard>
|
||||
<ChartHeader>
|
||||
<ChartTitle tooltip="This is a tooltip">Standard Bar Chart</ChartTitle>
|
||||
<ChartActions actions={actions} />
|
||||
</ChartHeader>
|
||||
<ChartContent
|
||||
isEmpty={data.length === 0}
|
||||
emptyState={
|
||||
<ChartEmptyState
|
||||
icon={<BarChart2 size={16} />}
|
||||
title="No data to show"
|
||||
description="It may take up to 24 hours for data to refresh"
|
||||
/>
|
||||
}
|
||||
loadingState={<ChartLoadingState />}
|
||||
>
|
||||
<div className="h-40">
|
||||
<ChartBar
|
||||
data={data}
|
||||
dataKey="standard_score"
|
||||
showGrid={false}
|
||||
showYAxis={true}
|
||||
YAxisProps={{
|
||||
tickFormatter: (value) => `${value}k`,
|
||||
width: 80,
|
||||
}}
|
||||
isFullHeight={true}
|
||||
/>
|
||||
</div>
|
||||
</ChartContent>
|
||||
<ChartFooter>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Timestamp</TableHead>
|
||||
<TableHead className="text-right">Standard Score</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data.slice(0, 3).map((item) => (
|
||||
<TableRow key={item.timestamp}>
|
||||
<TableCell className="text-foreground-light">
|
||||
{format(new Date(item.timestamp), 'MMM d, yyyy h:mm a')}
|
||||
</TableCell>
|
||||
<TableCell className="text-right tabular-nums">{item.standard_score}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</ChartFooter>
|
||||
</ChartCard>
|
||||
</Chart>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -3,8 +3,9 @@ const supabaseConfig = require('eslint-config-supabase/next')
|
||||
|
||||
module.exports = defineConfig([
|
||||
supabaseConfig,
|
||||
{ ignores: ['__registry__'] },
|
||||
{
|
||||
files: ['registry/**/*.tsx', '__registry__/**/*.tsx', 'app/**/*.tsx'],
|
||||
files: ['registry/**/*.tsx', 'app/**/*.tsx'],
|
||||
rules: {
|
||||
'no-restricted-exports': 'off',
|
||||
},
|
||||
|
||||
@@ -5,16 +5,17 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"dev": "run-p --race dev:*",
|
||||
"dev": "pnpm run generate:registry && run-p --race dev:*",
|
||||
"dev:next": "next dev --turbopack --port 3003",
|
||||
"dev:content": "contentlayer2 dev",
|
||||
"build": "pnpm run content:build && pnpm run build:registry && next build --turbopack",
|
||||
"build:registry": "tsx ./scripts/build-registry.mts && prettier --log-level silent --write \"registry/**/*.{ts,tsx,mdx}\" --cache",
|
||||
"build": "pnpm run build:registry && pnpm run content:build && next build --turbopack",
|
||||
"generate:registry": "tsx ./scripts/build-registry.mts",
|
||||
"build:registry": "pnpm run generate:registry && prettier --log-level silent --write \"registry/**/*.{ts,tsx,mdx}\" --cache",
|
||||
"start": "next start",
|
||||
"lint": "eslint .",
|
||||
"content:build": "contentlayer2 build",
|
||||
"clean": "rimraf .next .turbo tsconfig.tsbuildinfo",
|
||||
"typecheck": "contentlayer2 build && tsc --noEmit -p tsconfig.json"
|
||||
"clean": "rimraf .next .turbo tsconfig.tsbuildinfo __registry__",
|
||||
"typecheck": "pnpm run generate:registry && contentlayer2 build && tsc --noEmit -p tsconfig.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.1.1",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @sts-nocheck
|
||||
import { existsSync, promises as fs } from 'fs'
|
||||
import { promises as fs } from 'fs'
|
||||
import { tmpdir } from 'os'
|
||||
import path from 'path'
|
||||
import { cwd } from 'process'
|
||||
@@ -191,13 +191,10 @@ export const Index: Record<string, any> = {
|
||||
})
|
||||
)
|
||||
|
||||
// // Write the source file for blocks only.
|
||||
// Write the source file for blocks only.
|
||||
sourceFilename = `__registry__/${style.name}/${type}/${item.name}.tsx`
|
||||
const sourcePath = path.join(process.cwd(), sourceFilename)
|
||||
if (!existsSync(sourcePath)) {
|
||||
await fs.mkdir(sourcePath, { recursive: true })
|
||||
}
|
||||
|
||||
await fs.mkdir(path.dirname(sourcePath), { recursive: true })
|
||||
rimraf.sync(sourcePath)
|
||||
await fs.writeFile(sourcePath, sourceFile.getText())
|
||||
}
|
||||
@@ -273,8 +270,10 @@ export const Index: Record<string, any> = {
|
||||
// await fs.writeFile(path.join(REGISTRY_PATH, 'index.json'), registryJson, 'utf8')
|
||||
|
||||
// Write style index.
|
||||
rimraf.sync(path.join(process.cwd(), '__registry__/index.tsx'))
|
||||
await fs.writeFile(path.join(process.cwd(), '__registry__/index.tsx'), index)
|
||||
const indexPath = path.join(process.cwd(), '__registry__/index.tsx')
|
||||
await fs.mkdir(path.dirname(indexPath), { recursive: true })
|
||||
rimraf.sync(indexPath)
|
||||
await fs.writeFile(indexPath, index)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
Reference in new issue
Block a user