mirror of
https://github.com/supabase/supabase.git
synced 2026-10-11 20:35:07 +03:00
Merge branch 'master' into chore/refactor-filter
This commit is contained in:
commit
97ae9fa56c
1310 files changed
+122431
-102197
No files matched your search
@@ -0,0 +1,324 @@
|
||||
---
|
||||
description: E2E testing best practices for Playwright tests in Studio
|
||||
globs:
|
||||
- e2e/studio/**/*.ts
|
||||
- e2e/studio/**/*.spec.ts
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# E2E Testing Best Practices
|
||||
|
||||
## Getting Context
|
||||
|
||||
Before writing or modifying tests, use the Playwright MCP to understand:
|
||||
|
||||
- Available page elements and their roles/locators
|
||||
- Current page state and network activity
|
||||
- Existing test patterns in the codebase
|
||||
|
||||
Avoid extensive code reading - let Playwright's inspection tools guide your understanding of the UI.
|
||||
|
||||
## Avoiding Race Conditions
|
||||
|
||||
### Set up API waiters BEFORE triggering actions
|
||||
|
||||
The most common source of flaky tests is race conditions between UI actions and API calls. Always create response waiters before clicking buttons or navigating.
|
||||
|
||||
```ts
|
||||
// ❌ Bad - race condition: response might complete before waiter is set up
|
||||
await page.getByRole('button', { name: 'Save' }).click()
|
||||
await waitForApiResponse(page, 'pg-meta', ref, 'query?key=table-create')
|
||||
|
||||
// ✅ Good - waiter is ready before action
|
||||
const apiPromise = waitForApiResponse(page, 'pg-meta', ref, 'query?key=table-create')
|
||||
await page.getByRole('button', { name: 'Save' }).click()
|
||||
await apiPromise
|
||||
```
|
||||
|
||||
### Use `createApiResponseWaiter` for pre-navigation waits
|
||||
|
||||
When you need to wait for a response that happens during navigation:
|
||||
|
||||
```ts
|
||||
// ✅ Good - waiter created before navigation
|
||||
const loadPromise = waitForTableToLoad(page, ref)
|
||||
await page.goto(toUrl(`/project/${ref}/editor?schema=public`))
|
||||
await loadPromise
|
||||
```
|
||||
|
||||
### Wait for multiple related API calls with Promise.all
|
||||
|
||||
When an action triggers multiple API calls, wait for all of them:
|
||||
|
||||
```ts
|
||||
// ✅ Good - wait for all related API calls
|
||||
const createTablePromise = waitForApiResponseWithTimeout(page, (response) =>
|
||||
response.url().includes('query?key=table-create')
|
||||
)
|
||||
const tablesPromise = waitForApiResponseWithTimeout(page, (response) =>
|
||||
response.url().includes('tables?include_columns=true')
|
||||
)
|
||||
const entitiesPromise = waitForApiResponseWithTimeout(page, (response) =>
|
||||
response.url().includes('query?key=entity-types-')
|
||||
)
|
||||
|
||||
await page.getByRole('button', { name: 'Save' }).click()
|
||||
await Promise.all([createTablePromise, tablesPromise, entitiesPromise])
|
||||
```
|
||||
|
||||
## Waiting Strategies
|
||||
|
||||
### Prefer Playwright's built-in auto-waiting
|
||||
|
||||
Playwright automatically waits for elements to be actionable. Use this instead of manual timeouts:
|
||||
|
||||
```ts
|
||||
// ❌ Bad - arbitrary timeout
|
||||
await page.waitForTimeout(2000)
|
||||
await page.getByRole('button', { name: 'Submit' }).click()
|
||||
|
||||
// ✅ Good - auto-waits for element to be visible and enabled
|
||||
await page.getByRole('button', { name: 'Submit' }).click()
|
||||
```
|
||||
|
||||
### Use `expect.poll` for dynamic assertions
|
||||
|
||||
When waiting for state to change:
|
||||
|
||||
```ts
|
||||
// ✅ Good - polls until condition is met
|
||||
await expect
|
||||
.poll(async () => {
|
||||
return await page.getByLabel(`View ${tableName}`).count()
|
||||
})
|
||||
.toBe(0)
|
||||
```
|
||||
|
||||
### Use `waitForSelector` with state for element lifecycle
|
||||
|
||||
```ts
|
||||
// ✅ Good - wait for panel to close
|
||||
await page.waitForSelector('[data-testid="side-panel"]', { state: 'detached' })
|
||||
```
|
||||
|
||||
### Avoid `networkidle` - use specific API waits instead
|
||||
|
||||
```ts
|
||||
// ❌ Bad - unreliable and slow
|
||||
await page.waitForLoadState('networkidle')
|
||||
|
||||
// ✅ Good - wait for specific API response
|
||||
await waitForApiResponse(page, 'pg-meta', ref, 'tables')
|
||||
```
|
||||
|
||||
### Use timeouts sparingly and only for non-API waits
|
||||
|
||||
```ts
|
||||
// ✅ Acceptable - waiting for client-side debounce
|
||||
await page.getByRole('textbox').fill('search term')
|
||||
await page.waitForTimeout(300) // Allow debounce to complete
|
||||
|
||||
// ✅ Acceptable - waiting for clipboard API
|
||||
await page.evaluate(() => navigator.clipboard.readText())
|
||||
await page.waitForTimeout(500)
|
||||
```
|
||||
|
||||
## Test Structure
|
||||
|
||||
### Use the custom test utility
|
||||
|
||||
Always import from the custom test utility for consistent fixtures:
|
||||
|
||||
```ts
|
||||
import { test } from '../utils/test.js'
|
||||
```
|
||||
|
||||
### Use `withFileOnceSetup` for expensive setup
|
||||
|
||||
When setup is expensive (cleanup, seeding), run it once per file:
|
||||
|
||||
```ts
|
||||
test.beforeAll(async ({ browser, ref }) => {
|
||||
await withFileOnceSetup(import.meta.url, async () => {
|
||||
const ctx = await browser.newContext()
|
||||
const page = await ctx.newPage()
|
||||
|
||||
// Expensive setup logic (e.g., cleanup old test data)
|
||||
await deleteTestTables(page, ref)
|
||||
})
|
||||
})
|
||||
|
||||
test.afterAll(async () => {
|
||||
await releaseFileOnceCleanup(import.meta.url)
|
||||
})
|
||||
```
|
||||
|
||||
### Dismiss toasts before interacting with UI
|
||||
|
||||
Toasts can overlay buttons and block interactions:
|
||||
|
||||
```ts
|
||||
const dismissToastsIfAny = async (page: Page) => {
|
||||
const closeButtons = page.getByRole('button', { name: 'Close toast' })
|
||||
const count = await closeButtons.count()
|
||||
for (let i = 0; i < count; i++) {
|
||||
await closeButtons.nth(i).click()
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Good - dismiss toasts before clicking
|
||||
await dismissToastsIfAny(page)
|
||||
await page.getByRole('button', { name: 'New table' }).click()
|
||||
```
|
||||
|
||||
## Assertions
|
||||
|
||||
### Always include descriptive messages
|
||||
|
||||
```ts
|
||||
// ❌ Bad - no context on failure
|
||||
await expect(page.getByRole('button', { name: 'Save' })).toBeVisible()
|
||||
|
||||
// ✅ Good - clear message on failure
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Save' }),
|
||||
'Save button should be visible after form is filled'
|
||||
).toBeVisible()
|
||||
```
|
||||
|
||||
### Use appropriate timeouts for slow operations
|
||||
|
||||
```ts
|
||||
// ✅ Good - explicit timeout for slow operations
|
||||
await expect(
|
||||
page.getByText(`Table ${tableName} is good to go!`),
|
||||
'Success toast should be visible after table creation'
|
||||
).toBeVisible({ timeout: 50000 })
|
||||
```
|
||||
|
||||
## Locators
|
||||
|
||||
### Prefer role-based locators
|
||||
|
||||
```ts
|
||||
// ✅ Good - semantic and resilient
|
||||
page.getByRole('button', { name: 'Save' })
|
||||
page.getByRole('textbox', { name: 'Username' })
|
||||
page.getByRole('menuitem', { name: 'Delete' })
|
||||
|
||||
// ❌ Avoid - brittle CSS selectors
|
||||
page.locator('.btn-primary')
|
||||
page.locator('#submit-button')
|
||||
```
|
||||
|
||||
### Use test IDs for complex elements
|
||||
|
||||
```ts
|
||||
// ✅ Good - stable identifier for complex elements
|
||||
page.getByTestId('table-editor-side-panel')
|
||||
page.getByTestId('action-bar-save-row')
|
||||
```
|
||||
|
||||
### Use `filter` for finding elements in context
|
||||
|
||||
```ts
|
||||
// ✅ Good - find button within specific row
|
||||
const bucketRow = page.getByRole('row').filter({ hasText: bucketName })
|
||||
await bucketRow.getByRole('button').click()
|
||||
```
|
||||
|
||||
## Helper Functions
|
||||
|
||||
### Extract reusable operations into helpers
|
||||
|
||||
Create helper functions for common operations:
|
||||
|
||||
```ts
|
||||
// e2e/studio/utils/storage-helpers.ts
|
||||
export const createBucket = async (
|
||||
page: Page,
|
||||
ref: string,
|
||||
bucketName: string,
|
||||
isPublic: boolean = false
|
||||
) => {
|
||||
await navigateToStorageFiles(page, ref)
|
||||
|
||||
// Check if already exists
|
||||
const bucketRow = page.getByRole('row').filter({ hasText: bucketName })
|
||||
if ((await bucketRow.count()) > 0) return
|
||||
|
||||
await dismissToastsIfAny(page)
|
||||
|
||||
// Create bucket with proper waits
|
||||
const apiPromise = waitForApiResponse(page, 'storage', ref, 'bucket', { method: 'POST' })
|
||||
await page.getByRole('button', { name: 'New bucket' }).click()
|
||||
await page.getByRole('textbox', { name: 'Bucket name' }).fill(bucketName)
|
||||
await page.getByRole('button', { name: 'Create' }).click()
|
||||
await apiPromise
|
||||
|
||||
await expect(
|
||||
page.getByRole('row').filter({ hasText: bucketName }),
|
||||
`Bucket ${bucketName} should be visible`
|
||||
).toBeVisible()
|
||||
}
|
||||
```
|
||||
|
||||
### Use the existing wait utilities
|
||||
|
||||
```ts
|
||||
import {
|
||||
createApiResponseWaiter,
|
||||
waitForApiResponse,
|
||||
waitForGridDataToLoad,
|
||||
waitForTableToLoad,
|
||||
} from '../utils/wait-for-response.js'
|
||||
```
|
||||
|
||||
## API Mocking
|
||||
|
||||
### Mock APIs for isolated testing
|
||||
|
||||
```ts
|
||||
// ✅ Good - mock API response
|
||||
await page.route('*/**/logs.all*', async (route) => {
|
||||
await route.fulfill({ body: JSON.stringify(mockAPILogs) })
|
||||
})
|
||||
```
|
||||
|
||||
### Use soft waits for optional API calls
|
||||
|
||||
```ts
|
||||
// ✅ Good - don't fail if API doesn't respond
|
||||
await waitForApiResponse(page, 'pg-meta', ref, 'optional-endpoint', {
|
||||
soft: true,
|
||||
fallbackWaitMs: 1000,
|
||||
})
|
||||
```
|
||||
|
||||
## Cleanup
|
||||
|
||||
### Clean up test data in beforeAll/beforeEach
|
||||
|
||||
```ts
|
||||
test.beforeEach(async ({ page, ref }) => {
|
||||
await deleteAllBuckets(page, ref)
|
||||
})
|
||||
```
|
||||
|
||||
### Handle existing state gracefully
|
||||
|
||||
```ts
|
||||
// ✅ Good - check before trying to delete
|
||||
const bucketRow = page.getByRole('row').filter({ hasText: bucketName })
|
||||
if ((await bucketRow.count()) === 0) return
|
||||
// proceed with deletion
|
||||
```
|
||||
|
||||
### Reset local storage when needed
|
||||
|
||||
```ts
|
||||
import { resetLocalStorage } from '../utils/reset-local-storage.js'
|
||||
|
||||
// Clean up after tests that modify local storage
|
||||
await resetLocalStorage(page, ref)
|
||||
```
|
||||
@@ -0,0 +1,434 @@
|
||||
---
|
||||
description: React best practices and coding standards for Studio
|
||||
globs:
|
||||
- apps/studio/**/*.tsx
|
||||
- apps/studio/**/*.ts
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Studio Best Practices
|
||||
|
||||
## Boolean Handling
|
||||
|
||||
### Assign complex conditions to descriptive variables
|
||||
|
||||
When you have multiple conditions in a single expression, extract them into well-named boolean variables. This improves readability and makes the code self-documenting.
|
||||
|
||||
```tsx
|
||||
// ❌ Bad - complex inline condition
|
||||
{
|
||||
!isSchemaLocked && isTableLike(selectedTable) && canUpdateColumns && !isLoading && (
|
||||
<Button onClick={onAddColumn}>New column</Button>
|
||||
)
|
||||
}
|
||||
|
||||
// ✅ Good - extract to descriptive variables
|
||||
const isTableEntity = isTableLike(selectedTable)
|
||||
const canShowAddButton = !isSchemaLocked && isTableEntity && canUpdateColumns && !isLoading
|
||||
|
||||
{
|
||||
canShowAddButton && <Button onClick={onAddColumn}>New column</Button>
|
||||
}
|
||||
```
|
||||
|
||||
### Use consistent naming conventions for booleans
|
||||
|
||||
- Use `is` prefix for state/identity: `isLoading`, `isPaused`, `isNewRecord`, `isError`
|
||||
- Use `has` prefix for possession: `hasPermission`, `hasShownModal`, `hasData`
|
||||
- Use `can` prefix for capability/permission: `canUpdateColumns`, `canDelete`, `canEdit`
|
||||
- Use `should` prefix for conditional behavior: `shouldFetch`, `shouldRender`, `shouldValidate`
|
||||
|
||||
```tsx
|
||||
// ✅ Good examples from codebase
|
||||
const isNewRecord = column === undefined
|
||||
const isPaused = project?.status === PROJECT_STATUS.INACTIVE
|
||||
const isMatureProject = dayjs(project?.inserted_at).isBefore(dayjs().subtract(10, 'day'))
|
||||
const { can: canUpdateColumns } = useAsyncCheckPermissions(
|
||||
PermissionAction.TENANT_SQL_ADMIN_WRITE,
|
||||
'columns'
|
||||
)
|
||||
```
|
||||
|
||||
### Derive boolean state instead of storing it
|
||||
|
||||
When a boolean can be computed from existing state, derive it rather than storing it separately.
|
||||
|
||||
```tsx
|
||||
// ❌ Bad - storing derived state
|
||||
const [isFormValid, setIsFormValid] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
setIsFormValid(name.length > 0 && email.includes('@'))
|
||||
}, [name, email])
|
||||
|
||||
// ✅ Good - derive from existing state
|
||||
const isFormValid = name.length > 0 && email.includes('@')
|
||||
```
|
||||
|
||||
## Component Structure
|
||||
|
||||
### Break down large components
|
||||
|
||||
Components should ideally be under 200-300 lines. If a component grows larger, consider splitting it.
|
||||
|
||||
**Signs a component should be split:**
|
||||
|
||||
- Multiple distinct UI sections
|
||||
- Complex conditional rendering logic
|
||||
- Multiple useState hooks for unrelated state
|
||||
- Difficult to understand at a glance
|
||||
|
||||
```tsx
|
||||
// ❌ Bad - monolithic component with everything inline
|
||||
const UserDashboard = () => {
|
||||
// 50 lines of hooks and state
|
||||
// 100 lines of handlers
|
||||
// 300 lines of JSX with nested conditions
|
||||
}
|
||||
|
||||
// ✅ Good - split into focused sub-components
|
||||
const UserDashboard = () => {
|
||||
return (
|
||||
<div>
|
||||
<UserHeader />
|
||||
<UserStats />
|
||||
<UserActivitySection />
|
||||
<UserSettingsPanel />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Co-locate related components
|
||||
|
||||
Place sub-components in the same directory as the parent component. Use an index file for cleaner imports.
|
||||
|
||||
```
|
||||
components/interfaces/Auth/Users/
|
||||
├── UserPanel.tsx
|
||||
├── UserOverview.tsx
|
||||
├── UserLogs.tsx
|
||||
├── Users.constants.ts
|
||||
└── index.ts
|
||||
```
|
||||
|
||||
### Extract repeated JSX patterns
|
||||
|
||||
If you find yourself copying similar JSX blocks, extract them into a component.
|
||||
|
||||
```tsx
|
||||
// ❌ Bad - repeated pattern
|
||||
<TabsTrigger_Shadcn_ value="overview" className="px-0 pb-0 h-full text-xs data-[state=active]:bg-transparent !shadow-none">
|
||||
Overview
|
||||
</TabsTrigger_Shadcn_>
|
||||
<TabsTrigger_Shadcn_ value="logs" className="px-0 pb-0 h-full text-xs data-[state=active]:bg-transparent !shadow-none">
|
||||
Logs
|
||||
</TabsTrigger_Shadcn_>
|
||||
|
||||
// ✅ Good - extract to component
|
||||
const PanelTab = ({ value, children }: { value: string; children: ReactNode }) => (
|
||||
<TabsTrigger_Shadcn_
|
||||
value={value}
|
||||
className="px-0 pb-0 h-full text-xs data-[state=active]:bg-transparent !shadow-none"
|
||||
>
|
||||
{children}
|
||||
</TabsTrigger_Shadcn_>
|
||||
)
|
||||
```
|
||||
|
||||
## Loading and Error States
|
||||
|
||||
### Use consistent loading/error/success pattern
|
||||
|
||||
Follow a consistent pattern for handling async states:
|
||||
|
||||
```tsx
|
||||
const { data, error, isLoading, isError, isSuccess } = useQuery()
|
||||
|
||||
// Handle loading state first
|
||||
if (isLoading) {
|
||||
return <GenericSkeletonLoader />
|
||||
}
|
||||
|
||||
// Handle error state
|
||||
if (isError) {
|
||||
return <AlertError error={error} subject="Failed to load data" />
|
||||
}
|
||||
|
||||
// Handle empty state if needed
|
||||
if (isSuccess && data.length === 0) {
|
||||
return <EmptyState />
|
||||
}
|
||||
|
||||
// Render success state
|
||||
return <DataDisplay data={data} />
|
||||
```
|
||||
|
||||
### Use early returns for guard clauses
|
||||
|
||||
Prefer early returns over deeply nested conditionals:
|
||||
|
||||
```tsx
|
||||
// ❌ Bad - deeply nested
|
||||
const Component = () => {
|
||||
if (data) {
|
||||
if (!isError) {
|
||||
if (hasPermission) {
|
||||
return <ActualContent />
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// ✅ Good - early returns
|
||||
const Component = () => {
|
||||
if (!data) return null
|
||||
if (isError) return <ErrorDisplay />
|
||||
if (!hasPermission) return <PermissionDenied />
|
||||
|
||||
return <ActualContent />
|
||||
}
|
||||
```
|
||||
|
||||
## State Management
|
||||
|
||||
### Keep state as local as possible
|
||||
|
||||
Start with local state and lift up only when needed.
|
||||
|
||||
```tsx
|
||||
// ✅ Good - state lives where it's used
|
||||
const SearchableList = () => {
|
||||
const [filterString, setFilterString] = useState('')
|
||||
|
||||
const filteredItems = items.filter((item) => item.name.includes(filterString))
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Input value={filterString} onChange={(e) => setFilterString(e.target.value)} />
|
||||
<List items={filteredItems} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Group related state with objects or reducers
|
||||
|
||||
When you have multiple related pieces of state, consider grouping them:
|
||||
|
||||
```tsx
|
||||
// ❌ Bad - multiple related useState calls
|
||||
const [name, setName] = useState('')
|
||||
const [email, setEmail] = useState('')
|
||||
const [phone, setPhone] = useState('')
|
||||
|
||||
// ✅ Good - grouped state for forms (use react-hook-form)
|
||||
const form = useForm<FormValues>({
|
||||
defaultValues: { name: '', email: '', phone: '' },
|
||||
})
|
||||
```
|
||||
|
||||
## Custom Hooks
|
||||
|
||||
### Extract complex logic into custom hooks
|
||||
|
||||
When logic becomes reusable or complex, extract it:
|
||||
|
||||
```tsx
|
||||
// ✅ Good - extracted to custom hook
|
||||
export function useAsyncCheckPermissions(action: string, resource: string) {
|
||||
const { permissions, isLoading, isSuccess } = useGetProjectPermissions()
|
||||
|
||||
const can = useMemo(() => {
|
||||
if (!IS_PLATFORM) return true
|
||||
if (!isSuccess || !permissions) return false
|
||||
return doPermissionsCheck(permissions, action, resource)
|
||||
}, [isSuccess, permissions, action, resource])
|
||||
|
||||
return { isLoading, isSuccess, can }
|
||||
}
|
||||
|
||||
// Usage
|
||||
const { can: canUpdateColumns } = useAsyncCheckPermissions(
|
||||
PermissionAction.TENANT_SQL_ADMIN_WRITE,
|
||||
'columns'
|
||||
)
|
||||
```
|
||||
|
||||
### Return objects from hooks for better extensibility
|
||||
|
||||
```tsx
|
||||
// ❌ Bad - returning array (hard to extend)
|
||||
const useToggle = () => {
|
||||
const [value, setValue] = useState(false)
|
||||
return [value, () => setValue((v) => !v)]
|
||||
}
|
||||
|
||||
// ✅ Good - returning object (easy to extend)
|
||||
const useToggle = (initial = false) => {
|
||||
const [value, setValue] = useState(initial)
|
||||
return {
|
||||
value,
|
||||
toggle: () => setValue((v) => !v),
|
||||
setTrue: () => setValue(true),
|
||||
setFalse: () => setValue(false),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Event Handlers
|
||||
|
||||
### Name handlers consistently
|
||||
|
||||
Use `on` prefix for prop callbacks and `handle` prefix for internal handlers:
|
||||
|
||||
```tsx
|
||||
interface Props {
|
||||
onClose: () => void // Callback prop
|
||||
onSave: (data: Data) => void
|
||||
}
|
||||
|
||||
const Component = ({ onClose, onSave }: Props) => {
|
||||
const handleSubmit = () => {
|
||||
// Internal handler
|
||||
// process data
|
||||
onSave(data)
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
// cleanup
|
||||
onClose()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Avoid inline arrow functions for expensive operations
|
||||
|
||||
```tsx
|
||||
// ❌ Bad - creates new function every render
|
||||
<ExpensiveList
|
||||
items={items}
|
||||
onItemClick={(item) => handleItemClick(item)}
|
||||
/>
|
||||
|
||||
// ✅ Good - stable reference with useCallback
|
||||
const handleItemClick = useCallback((item: Item) => {
|
||||
// handle click
|
||||
}, [dependencies])
|
||||
|
||||
<ExpensiveList items={items} onItemClick={handleItemClick} />
|
||||
```
|
||||
|
||||
## Conditional Rendering
|
||||
|
||||
### Use appropriate patterns for different scenarios
|
||||
|
||||
```tsx
|
||||
// Simple show/hide - use &&
|
||||
{
|
||||
isVisible && <Component />
|
||||
}
|
||||
|
||||
// Binary choice - use ternary
|
||||
{
|
||||
isLoading ? <Spinner /> : <Content />
|
||||
}
|
||||
|
||||
// Multiple conditions - use early returns or extracted component
|
||||
const StatusDisplay = ({ status }: { status: Status }) => {
|
||||
if (status === 'loading') return <Spinner />
|
||||
if (status === 'error') return <ErrorMessage />
|
||||
if (status === 'empty') return <EmptyState />
|
||||
return <DataDisplay />
|
||||
}
|
||||
```
|
||||
|
||||
### Avoid nested ternaries
|
||||
|
||||
```tsx
|
||||
// ❌ Bad - nested ternary
|
||||
{
|
||||
isLoading ? <Spinner /> : isError ? <Error /> : <Content />
|
||||
}
|
||||
|
||||
// ✅ Good - separate conditions or early returns
|
||||
if (isLoading) return <Spinner />
|
||||
if (isError) return <Error />
|
||||
return <Content />
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
### Use useMemo for expensive computations
|
||||
|
||||
```tsx
|
||||
// ✅ Good - memoize expensive filtering
|
||||
const filteredItems = useMemo(
|
||||
() => items.filter((item) => item.name.toLowerCase().includes(searchQuery.toLowerCase())),
|
||||
[items, searchQuery]
|
||||
)
|
||||
```
|
||||
|
||||
### Avoid premature optimization
|
||||
|
||||
Don't wrap everything in useMemo/useCallback. Only optimize when:
|
||||
|
||||
- You have measured a performance problem
|
||||
- The computation is genuinely expensive
|
||||
- The value is passed to memoized children
|
||||
|
||||
## TypeScript
|
||||
|
||||
### Define prop interfaces explicitly
|
||||
|
||||
```tsx
|
||||
interface UserCardProps {
|
||||
user: User
|
||||
onEdit: (user: User) => void
|
||||
onDelete: (userId: string) => void
|
||||
isEditable?: boolean
|
||||
}
|
||||
|
||||
export const UserCard = ({ user, onEdit, onDelete, isEditable = true }: UserCardProps) => {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Use discriminated unions for complex state
|
||||
|
||||
```tsx
|
||||
type AsyncState<T> =
|
||||
| { status: 'idle' }
|
||||
| { status: 'loading' }
|
||||
| { status: 'success'; data: T }
|
||||
| { status: 'error'; error: Error }
|
||||
```
|
||||
|
||||
### Avoid type casting, prefer validation with zod
|
||||
|
||||
Never use type casting (e.g., `as any`, `as Type`). Instead, validate values at runtime using zod schemas. This ensures type safety and catches runtime errors.
|
||||
|
||||
```tsx
|
||||
// ❌ Bad - type casting bypasses type checking
|
||||
const user = apiResponse as User
|
||||
const data = unknownValue as string
|
||||
|
||||
// ✅ Good - validate with zod schema
|
||||
const userSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
email: z.string().email(),
|
||||
})
|
||||
|
||||
const user = userSchema.parse(apiResponse)
|
||||
const data = z.string().parse(unknownValue)
|
||||
|
||||
// ✅ Good - safe parsing with error handling
|
||||
const result = userSchema.safeParse(apiResponse)
|
||||
if (result.success) {
|
||||
const user = result.data
|
||||
} else {
|
||||
// handle validation errors
|
||||
}
|
||||
```
|
||||
+173
-26
@@ -125,63 +125,106 @@ export const MyPageComponent = () => (
|
||||
|
||||
## Forms
|
||||
|
||||
- Build forms with `react-hook-form` + `zod`.
|
||||
- Use our `_Shadcn_` form primitives from `ui` and prefer `FormItemLayout` with layout="flex-row-reverse" for most controls (see `apps/studio/components/interfaces/Settings/Integrations/GithubIntegration/GitHubIntegrationConnectionForm.tsx`).
|
||||
- Keep imports from `ui` with `_Shadcn_` suffixes.
|
||||
- Forms should generally be wrapped in a Card unless specified
|
||||
- If the submit button is outside the form, add a new variable named formId outside the component, and set it as property id on the form element and formId on the button.
|
||||
Forms in Supabase Studio should follow consistent patterns to ensure a cohesive user experience across settings pages and side panels.
|
||||
|
||||
### Example (single field)
|
||||
### Core Principles
|
||||
|
||||
- Build forms with `react-hook-form` + `zod`
|
||||
- Always use `FormItemLayout` instead of manually composing `FormItem`, `FormLabel`, `FormMessage`, and `FormDescription`
|
||||
- Always wrap form inputs with `FormControl_Shadcn_` to ensure proper form integration
|
||||
- Keep imports from `ui` with `_Shadcn_` suffixes
|
||||
- Handle dirty state: Show cancel buttons and disable save buttons based on `form.formState.isDirty`
|
||||
- Show loading states on submit buttons using the `loading` prop
|
||||
- If the submit button is outside the form, add a `formId` variable outside the component, set it as `id` on the form element and `form` prop on the button
|
||||
|
||||
### Layout Selection
|
||||
|
||||
- **Page layouts**: Use `FormItemLayout` with `layout="flex-row-reverse"` for horizontal alignment. Forms should be wrapped in a `Card` with each form field in its own `CardContent`, and `CardFooter` for actions. The layout automatically handles consistent input widths (50% on md, 40% on xl, min-w-100).
|
||||
- **Side panels (wide)**: Use `FormItemLayout` with `layout="horizontal"`. Use `SheetSection` to wrap each field group.
|
||||
- **Side panels (narrow, size="sm" or below)**: Use `FormItemLayout` with `layout="vertical"`
|
||||
|
||||
### Page Layout Form Example
|
||||
|
||||
```tsx
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import * as z from 'zod'
|
||||
|
||||
import { Button, Form_Shadcn_, FormField_Shadcn_, FormControl_Shadcn_, Input_Shadcn_ } from 'ui'
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
CardFooter,
|
||||
Form_Shadcn_,
|
||||
FormField_Shadcn_,
|
||||
FormControl_Shadcn_,
|
||||
Input_Shadcn_,
|
||||
Switch,
|
||||
} from 'ui'
|
||||
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
|
||||
|
||||
const profileSchema = z.object({
|
||||
username: z.string().min(2, 'Username must be at least 2 characters'),
|
||||
const formSchema = z.object({
|
||||
name: z.string().min(1, 'Name is required'),
|
||||
enableFeature: z.boolean(),
|
||||
})
|
||||
|
||||
const formId = `profile-form`
|
||||
|
||||
export function ProfileForm() {
|
||||
const form = useForm<z.infer<typeof profileSchema>>({
|
||||
resolver: zodResolver(profileSchema),
|
||||
defaultValues: { username: '' },
|
||||
export function SettingsForm() {
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: { name: '', enableFeature: false },
|
||||
mode: 'onSubmit',
|
||||
reValidateMode: 'onBlur',
|
||||
})
|
||||
|
||||
function onSubmit(values: z.infer<typeof profileSchema>) {
|
||||
// handle values
|
||||
function onSubmit(values: z.infer<typeof formSchema>) {
|
||||
// handle mutation with onSuccess/onError toast
|
||||
}
|
||||
|
||||
return (
|
||||
<Form_Shadcn_ {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<Card>
|
||||
<CardContent className="space-y-6">
|
||||
<CardContent>
|
||||
<FormField_Shadcn_
|
||||
control={form.control}
|
||||
name="username"
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItemLayout
|
||||
layout="flex-row-reverse"
|
||||
label="Username"
|
||||
description="This is your public display name."
|
||||
label="Name"
|
||||
description="A descriptive name for this resource"
|
||||
>
|
||||
<FormControl_Shadcn_>
|
||||
<Input_Shadcn_ placeholder="shadcn" autoComplete="off" {...field} />
|
||||
<Input_Shadcn_ {...field} placeholder="Enter name" />
|
||||
</FormControl_Shadcn_>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
<CardFooter className="justify-end">
|
||||
<Button type="primary" htmlType="submit">
|
||||
<CardContent>
|
||||
<FormField_Shadcn_
|
||||
control={form.control}
|
||||
name="enableFeature"
|
||||
render={({ field }) => (
|
||||
<FormItemLayout
|
||||
layout="flex-row-reverse"
|
||||
label="Enable Feature"
|
||||
description="Toggle this feature on or off"
|
||||
>
|
||||
<FormControl_Shadcn_>
|
||||
<Switch checked={field.value} onCheckedChange={field.onChange} />
|
||||
</FormControl_Shadcn_>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
<CardFooter className="justify-end space-x-2">
|
||||
{form.formState.isDirty && (
|
||||
<Button type="default" onClick={() => form.reset()}>
|
||||
Cancel
|
||||
</Button>
|
||||
)}
|
||||
<Button type="primary" htmlType="submit" disabled={!form.formState.isDirty}>
|
||||
Submit
|
||||
</Button>
|
||||
</CardFooter>
|
||||
@@ -192,6 +235,106 @@ export function ProfileForm() {
|
||||
}
|
||||
```
|
||||
|
||||
### Side Panel Form Example
|
||||
|
||||
```tsx
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useState } from 'react'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import * as z from 'zod'
|
||||
|
||||
import {
|
||||
Button,
|
||||
Form_Shadcn_,
|
||||
FormField_Shadcn_,
|
||||
FormControl_Shadcn_,
|
||||
Input_Shadcn_,
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetFooter,
|
||||
SheetHeader,
|
||||
SheetSection,
|
||||
SheetTitle,
|
||||
} from 'ui'
|
||||
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
|
||||
|
||||
const formSchema = z.object({
|
||||
name: z.string().min(1, 'Name is required'),
|
||||
})
|
||||
|
||||
const formId = 'sidepanel-form'
|
||||
|
||||
export function CreateResourcePanel() {
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: { name: '' },
|
||||
})
|
||||
|
||||
function onSubmit(values: z.infer<typeof formSchema>) {
|
||||
// handle mutation
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<Sheet open={open} onOpenChange={setOpen}>
|
||||
<SheetContent size="lg" className="flex flex-col gap-0">
|
||||
<SheetHeader>
|
||||
<SheetTitle>Create Resource</SheetTitle>
|
||||
</SheetHeader>
|
||||
<Form_Shadcn_ {...form}>
|
||||
<form
|
||||
id={formId}
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="overflow-auto flex-grow px-0"
|
||||
>
|
||||
<SheetSection>
|
||||
<FormField_Shadcn_
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItemLayout layout="horizontal" label="Name" description="A descriptive name">
|
||||
<FormControl_Shadcn_ className="col-span-6 min-w-100">
|
||||
<Input_Shadcn_ {...field} placeholder="Enter name" />
|
||||
</FormControl_Shadcn_>
|
||||
</FormItemLayout>
|
||||
)}
|
||||
/>
|
||||
</SheetSection>
|
||||
</form>
|
||||
</Form_Shadcn_>
|
||||
<SheetFooter>
|
||||
<Button type="default" onClick={() => setOpen(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="primary" form={formId} htmlType="submit">
|
||||
Create
|
||||
</Button>
|
||||
</SheetFooter>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Common Form Field Types
|
||||
|
||||
- **Text Input**: `Input_Shadcn_` with `placeholder`
|
||||
- **Password Input**: `Input_Shadcn_` with `type="password"`
|
||||
- **Number Input**: `Input_Shadcn_` with `type="number"` and `onChange={(e) => field.onChange(Number(e.target.value))}`
|
||||
- **Input with Units**: Wrap `Input_Shadcn_` with `PrePostTab` component: `<PrePostTab postTab="MB"><Input_Shadcn_ /></PrePostTab>`
|
||||
- **Textarea**: `Textarea` component with `rows` and `className="resize-none"`
|
||||
- **Switch**: `Switch` with `checked={field.value} onCheckedChange={field.onChange}`
|
||||
- **Checkbox**: `Checkbox_Shadcn_` with label, use multiple for checkbox groups
|
||||
- **Select**: `Select_Shadcn_` with `SelectTrigger_Shadcn_`, `SelectContent_Shadcn_`, `SelectItem_Shadcn_`
|
||||
- **Multi-Select**: Use `MultiSelector` from `ui-patterns/multi-select`
|
||||
- **Radio Group**: `RadioGroupStacked` with `RadioGroupStackedItem` for stacked options with descriptions
|
||||
- **Date Picker**: `Calendar` inside `Popover_Shadcn_` with a trigger button
|
||||
- **Copyable Input**: Use `Input` from `ui-patterns/DataInputs/Input` with `copy` and `readOnly` props
|
||||
- **Field Array**: Use `useFieldArray` from `react-hook-form` for dynamic add/remove fields
|
||||
- **Action Field**: Use `FormItemLayout` without form control, just buttons for navigation or performable actions. Wrap buttons in a div with `justify-end` to align them to the right
|
||||
|
||||
## Cards
|
||||
|
||||
- Use cards when needing to group related pieces of information
|
||||
@@ -203,6 +346,11 @@ export function ProfileForm() {
|
||||
## Sheets
|
||||
|
||||
- Use a sheet when needing to reveal more complicated forms or information relating to an object and context switching away to a new page would be disruptive e.g. we list auth providers, clicking an auth provider opens a sheet with information about that provider and a form to enable, user can close sheet to go back to providers list
|
||||
- Use `SheetContent` with `size="lg"` for forms that need horizontal layout
|
||||
- Use `SheetHeader`, `SheetTitle`, `SheetSection`, and `SheetFooter` for consistent structure
|
||||
- Place submit/cancel buttons in `SheetFooter`
|
||||
- For forms in sheets, use `FormItemLayout` with `layout="horizontal"` for wider panels or `layout="vertical"` for narrow panels (size="sm" or below)
|
||||
- See the Forms section for a complete side panel form example
|
||||
|
||||
## React Query
|
||||
|
||||
@@ -223,7 +371,6 @@ export function ProfileForm() {
|
||||
|
||||
```jsx
|
||||
import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from 'ui'
|
||||
|
||||
;<Table>
|
||||
<TableCaption>A list of your recent invoices.</TableCaption>
|
||||
<TableHeader>
|
||||
|
||||
@@ -18,3 +18,5 @@
|
||||
/apps/studio/components/interfaces/Billing/Payment @supabase/security
|
||||
/apps/studio/components/interfaces/Organization/Documents/ @supabase/security
|
||||
/apps/studio/pages/new/index.tsx @supabase/security
|
||||
|
||||
/apps/studio/data/sql/queries/ @supabase/postgres @avallete
|
||||
@@ -10,9 +10,6 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
autofix:
|
||||
runs-on: blacksmith-2vcpu-ubuntu-2404
|
||||
@@ -23,10 +20,18 @@ jobs:
|
||||
- name: Calculate number of commits
|
||||
run: echo "PR_FETCH_DEPTH=$(( ${{ github.event.pull_request.commits }} + 1 ))" >> "${GITHUB_ENV}"
|
||||
|
||||
- name: Generate token
|
||||
id: app-token
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
app-id: ${{ secrets.GH_AUTOFIX_APP_ID }}
|
||||
private-key: ${{ secrets.GH_AUTOFIX_PRIVATE_KEY }}
|
||||
permission-contents: write
|
||||
|
||||
- uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
token: ${{ secrets.PAT_AUTOFIX }}
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
fetch-depth: ${{ env.PR_FETCH_DEPTH }}
|
||||
sparse-checkout: |
|
||||
packages
|
||||
|
||||
@@ -10,8 +10,7 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -21,6 +20,16 @@ jobs:
|
||||
with:
|
||||
ref: master
|
||||
- uses: sobolevn/misspell-fixer-action@06ff0b508d4f4c0ba70d15f9a628232c0aade536 # v0.1.0
|
||||
|
||||
- name: Generate token
|
||||
id: app-token
|
||||
uses: actions/create-github-app-token@v2
|
||||
with:
|
||||
app-id: ${{ secrets.GH_AUTOFIX_APP_ID }}
|
||||
private-key: ${{ secrets.GH_AUTOFIX_PRIVATE_KEY }}
|
||||
permission-contents: write
|
||||
permission-pull-requests: write
|
||||
|
||||
- uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6.1.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
@@ -16,8 +16,9 @@ permissions:
|
||||
|
||||
jobs:
|
||||
typecheck:
|
||||
# Uses larger hosted runner as it significantly decreases build times
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
runs-on: blacksmith-2vcpu-ubuntu-2404
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=4096
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -38,7 +39,7 @@ jobs:
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Run TypeScript type check
|
||||
run: pnpm exec turbo run typecheck
|
||||
run: pnpm run typecheck
|
||||
|
||||
- name: Run Lint
|
||||
run: pnpm exec turbo run lint
|
||||
run: pnpm run lint
|
||||
@@ -96,11 +96,11 @@ Our approach for client libraries is modular. Each sub-library is a standalone i
|
||||
<tr>
|
||||
<td>JavaScript (TypeScript)</td>
|
||||
<td><a href="https://github.com/supabase/supabase-js" target="_blank" rel="noopener noreferrer">supabase-js</a></td>
|
||||
<td><a href="https://github.com/supabase/postgrest-js" target="_blank" rel="noopener noreferrer">postgrest-js</a></td>
|
||||
<td><a href="https://github.com/supabase/gotrue-js" target="_blank" rel="noopener noreferrer">gotrue-js</a></td>
|
||||
<td><a href="https://github.com/supabase/realtime-js" target="_blank" rel="noopener noreferrer">realtime-js</a></td>
|
||||
<td><a href="https://github.com/supabase/storage-js" target="_blank" rel="noopener noreferrer">storage-js</a></td>
|
||||
<td><a href="https://github.com/supabase/functions-js" target="_blank" rel="noopener noreferrer">functions-js</a></td>
|
||||
<td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/postgrest-js" target="_blank" rel="noopener noreferrer">postgrest-js</a></td>
|
||||
<td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/auth-js" target="_blank" rel="noopener noreferrer">auth-js</a></td>
|
||||
<td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/realtime-js" target="_blank" rel="noopener noreferrer">realtime-js</a></td>
|
||||
<td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/storage-js" target="_blank" rel="noopener noreferrer">storage-js</a></td>
|
||||
<td><a href="https://github.com/supabase/supabase-js/tree/master/packages/core/functions-js" target="_blank" rel="noopener noreferrer">functions-js</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Flutter</td>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"@payloadcms/db-postgres": "^3.52.0",
|
||||
"@payloadcms/live-preview-react": "^3.52.0",
|
||||
"@payloadcms/next": "3.52.0",
|
||||
"@payloadcms/payload-cloud": "3.52.0",
|
||||
"@payloadcms/payload-cloud": "3.60.0",
|
||||
"@payloadcms/plugin-form-builder": "3.52.0",
|
||||
"@payloadcms/plugin-nested-docs": "3.52.0",
|
||||
"@payloadcms/plugin-seo": "3.52.0",
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'An area chart with axes'
|
||||
|
||||
const chartData = [
|
||||
{ month: 'January', desktop: 186, mobile: 80 },
|
||||
{ month: 'February', desktop: 305, mobile: 200 },
|
||||
{ month: 'March', desktop: 237, mobile: 120 },
|
||||
{ month: 'April', desktop: 73, mobile: 190 },
|
||||
{ month: 'May', desktop: 209, mobile: 130 },
|
||||
{ month: 'June', desktop: 214, mobile: 140 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
mobile: {
|
||||
label: 'Mobile',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Area Chart - Axes</CardTitle>
|
||||
<CardDescription>Showing total visitors for the last 6 months</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<AreaChart
|
||||
accessibilityLayer
|
||||
data={chartData}
|
||||
margin={{
|
||||
left: -20,
|
||||
right: 12,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="month"
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
tickFormatter={(value) => value.slice(0, 3)}
|
||||
/>
|
||||
<YAxis tickLine={false} axisLine={false} tickMargin={8} tickCount={3} />
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent />} />
|
||||
<Area
|
||||
dataKey="mobile"
|
||||
type="natural"
|
||||
fill="var(--color-mobile)"
|
||||
fillOpacity={0.4}
|
||||
stroke="var(--color-mobile)"
|
||||
stackId="a"
|
||||
/>
|
||||
<Area
|
||||
dataKey="desktop"
|
||||
type="natural"
|
||||
fill="var(--color-desktop)"
|
||||
fillOpacity={0.4}
|
||||
stroke="var(--color-desktop)"
|
||||
stackId="a"
|
||||
/>
|
||||
</AreaChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<div className="flex w-full items-start gap-2 text-sm">
|
||||
<div className="grid gap-2">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2 leading-none text-muted-foreground">
|
||||
January - June 2024
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { Area, AreaChart, CartesianGrid, XAxis } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A simple area chart'
|
||||
|
||||
const chartData = [
|
||||
{ month: 'January', desktop: 186 },
|
||||
{ month: 'February', desktop: 305 },
|
||||
{ month: 'March', desktop: 237 },
|
||||
{ month: 'April', desktop: 73 },
|
||||
{ month: 'May', desktop: 209 },
|
||||
{ month: 'June', desktop: 214 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Area Chart</CardTitle>
|
||||
<CardDescription>Showing total visitors for the last 6 months</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<AreaChart
|
||||
accessibilityLayer
|
||||
data={chartData}
|
||||
margin={{
|
||||
left: 12,
|
||||
right: 12,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="month"
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
tickFormatter={(value) => value.slice(0, 3)}
|
||||
/>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent indicator="line" />} />
|
||||
<Area
|
||||
dataKey="desktop"
|
||||
type="natural"
|
||||
fill="var(--color-desktop)"
|
||||
fillOpacity={0.4}
|
||||
stroke="var(--color-desktop)"
|
||||
/>
|
||||
</AreaChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<div className="flex w-full items-start gap-2 text-sm">
|
||||
<div className="grid gap-2">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2 leading-none text-muted-foreground">
|
||||
January - June 2024
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { Area, AreaChart, CartesianGrid, XAxis } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'An area chart with gradient fill'
|
||||
|
||||
const chartData = [
|
||||
{ month: 'January', desktop: 186, mobile: 80 },
|
||||
{ month: 'February', desktop: 305, mobile: 200 },
|
||||
{ month: 'March', desktop: 237, mobile: 120 },
|
||||
{ month: 'April', desktop: 73, mobile: 190 },
|
||||
{ month: 'May', desktop: 209, mobile: 130 },
|
||||
{ month: 'June', desktop: 214, mobile: 140 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
mobile: {
|
||||
label: 'Mobile',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Area Chart - Gradient</CardTitle>
|
||||
<CardDescription>Showing total visitors for the last 6 months</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<AreaChart
|
||||
accessibilityLayer
|
||||
data={chartData}
|
||||
margin={{
|
||||
left: 12,
|
||||
right: 12,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="month"
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
tickFormatter={(value) => value.slice(0, 3)}
|
||||
/>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent />} />
|
||||
<defs>
|
||||
<linearGradient id="fillDesktop" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor="var(--color-desktop)" stopOpacity={0.8} />
|
||||
<stop offset="95%" stopColor="var(--color-desktop)" stopOpacity={0.1} />
|
||||
</linearGradient>
|
||||
<linearGradient id="fillMobile" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor="var(--color-mobile)" stopOpacity={0.8} />
|
||||
<stop offset="95%" stopColor="var(--color-mobile)" stopOpacity={0.1} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<Area
|
||||
dataKey="mobile"
|
||||
type="natural"
|
||||
fill="url(#fillMobile)"
|
||||
fillOpacity={0.4}
|
||||
stroke="var(--color-mobile)"
|
||||
stackId="a"
|
||||
/>
|
||||
<Area
|
||||
dataKey="desktop"
|
||||
type="natural"
|
||||
fill="url(#fillDesktop)"
|
||||
fillOpacity={0.4}
|
||||
stroke="var(--color-desktop)"
|
||||
stackId="a"
|
||||
/>
|
||||
</AreaChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<div className="flex w-full items-start gap-2 text-sm">
|
||||
<div className="grid gap-2">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2 leading-none text-muted-foreground">
|
||||
January - June 2024
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingDown, TrendingUp } from 'lucide-react'
|
||||
import { Area, AreaChart, CartesianGrid, XAxis } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import {
|
||||
ChartConfig,
|
||||
ChartContainer,
|
||||
ChartLegend,
|
||||
ChartLegendContent,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
} from 'ui'
|
||||
|
||||
export const description = 'An area chart with icons'
|
||||
|
||||
const chartData = [
|
||||
{ month: 'January', desktop: 186, mobile: 80 },
|
||||
{ month: 'February', desktop: 305, mobile: 200 },
|
||||
{ month: 'March', desktop: 237, mobile: 120 },
|
||||
{ month: 'April', desktop: 73, mobile: 190 },
|
||||
{ month: 'May', desktop: 209, mobile: 130 },
|
||||
{ month: 'June', desktop: 214, mobile: 140 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
icon: TrendingDown,
|
||||
},
|
||||
mobile: {
|
||||
label: 'Mobile',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
icon: TrendingUp,
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Area Chart - Icons</CardTitle>
|
||||
<CardDescription>Showing total visitors for the last 6 months</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<AreaChart
|
||||
accessibilityLayer
|
||||
data={chartData}
|
||||
margin={{
|
||||
left: 12,
|
||||
right: 12,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="month"
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
tickFormatter={(value) => value.slice(0, 3)}
|
||||
/>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent indicator="line" />} />
|
||||
<Area
|
||||
dataKey="mobile"
|
||||
type="natural"
|
||||
fill="var(--color-mobile)"
|
||||
fillOpacity={0.4}
|
||||
stroke="var(--color-mobile)"
|
||||
stackId="a"
|
||||
/>
|
||||
<Area
|
||||
dataKey="desktop"
|
||||
type="natural"
|
||||
fill="var(--color-desktop)"
|
||||
fillOpacity={0.4}
|
||||
stroke="var(--color-desktop)"
|
||||
stackId="a"
|
||||
/>
|
||||
<ChartLegend content={<ChartLegendContent />} />
|
||||
</AreaChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<div className="flex w-full items-start gap-2 text-sm">
|
||||
<div className="grid gap-2">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2 leading-none text-muted-foreground">
|
||||
January - June 2024
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,238 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import { Area, AreaChart, CartesianGrid, XAxis } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from 'ui'
|
||||
import {
|
||||
ChartConfig,
|
||||
ChartContainer,
|
||||
ChartLegend,
|
||||
ChartLegendContent,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
} from 'ui'
|
||||
import {
|
||||
Select_Shadcn_,
|
||||
SelectContent_Shadcn_,
|
||||
SelectItem_Shadcn_,
|
||||
SelectTrigger_Shadcn_,
|
||||
SelectValue_Shadcn_,
|
||||
} from 'ui'
|
||||
|
||||
export const description = 'An interactive area 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 = {
|
||||
visitors: {
|
||||
label: 'Visitors',
|
||||
},
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
mobile: {
|
||||
label: 'Mobile',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
const [timeRange, setTimeRange] = React.useState('90d')
|
||||
|
||||
const filteredData = chartData.filter((item) => {
|
||||
const date = new Date(item.date)
|
||||
const now = new Date()
|
||||
let daysToSubtract = 90
|
||||
if (timeRange === '30d') {
|
||||
daysToSubtract = 30
|
||||
} else if (timeRange === '7d') {
|
||||
daysToSubtract = 7
|
||||
}
|
||||
now.setDate(now.getDate() - daysToSubtract)
|
||||
return date >= now
|
||||
})
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="flex items-center gap-2 space-y-0 border-b py-5 sm:flex-row">
|
||||
<div className="grid flex-1 gap-1 text-center sm:text-left">
|
||||
<CardTitle>Area Chart - Interactive</CardTitle>
|
||||
<CardDescription>Showing total visitors for the last 3 months</CardDescription>
|
||||
</div>
|
||||
<Select_Shadcn_ value={timeRange} onValueChange={setTimeRange}>
|
||||
<SelectTrigger_Shadcn_
|
||||
className="w-[160px] rounded-lg sm:ml-auto"
|
||||
aria-label="Select a value"
|
||||
>
|
||||
<SelectValue_Shadcn_ placeholder="Last 3 months" />
|
||||
</SelectTrigger_Shadcn_>
|
||||
<SelectContent_Shadcn_ className="rounded-xl">
|
||||
<SelectItem_Shadcn_ value="90d" className="rounded-lg">
|
||||
Last 3 months
|
||||
</SelectItem_Shadcn_>
|
||||
<SelectItem_Shadcn_ value="30d" className="rounded-lg">
|
||||
Last 30 days
|
||||
</SelectItem_Shadcn_>
|
||||
<SelectItem_Shadcn_ value="7d" className="rounded-lg">
|
||||
Last 7 days
|
||||
</SelectItem_Shadcn_>
|
||||
</SelectContent_Shadcn_>
|
||||
</Select_Shadcn_>
|
||||
</CardHeader>
|
||||
<CardContent className="px-2 pt-4 sm:px-6 sm:pt-6">
|
||||
<ChartContainer config={chartConfig} className="aspect-auto h-[250px] w-full">
|
||||
<AreaChart data={filteredData}>
|
||||
<defs>
|
||||
<linearGradient id="fillDesktop" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor="var(--color-desktop)" stopOpacity={0.8} />
|
||||
<stop offset="95%" stopColor="var(--color-desktop)" stopOpacity={0.1} />
|
||||
</linearGradient>
|
||||
<linearGradient id="fillMobile" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor="var(--color-mobile)" stopOpacity={0.8} />
|
||||
<stop offset="95%" stopColor="var(--color-mobile)" stopOpacity={0.1} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<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
|
||||
cursor={false}
|
||||
content={
|
||||
<ChartTooltipContent
|
||||
labelFormatter={(value) => {
|
||||
return new Date(value).toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
})
|
||||
}}
|
||||
indicator="dot"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Area
|
||||
dataKey="mobile"
|
||||
type="natural"
|
||||
fill="url(#fillMobile)"
|
||||
stroke="var(--color-mobile)"
|
||||
stackId="a"
|
||||
/>
|
||||
<Area
|
||||
dataKey="desktop"
|
||||
type="natural"
|
||||
fill="url(#fillDesktop)"
|
||||
stroke="var(--color-desktop)"
|
||||
stackId="a"
|
||||
/>
|
||||
<ChartLegend content={<ChartLegendContent />} />
|
||||
</AreaChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { Area, AreaChart, CartesianGrid, XAxis } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import {
|
||||
ChartConfig,
|
||||
ChartContainer,
|
||||
ChartLegend,
|
||||
ChartLegendContent,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
} from 'ui'
|
||||
|
||||
export const description = 'An area chart with a legend'
|
||||
|
||||
const chartData = [
|
||||
{ month: 'January', desktop: 186, mobile: 80 },
|
||||
{ month: 'February', desktop: 305, mobile: 200 },
|
||||
{ month: 'March', desktop: 237, mobile: 120 },
|
||||
{ month: 'April', desktop: 73, mobile: 190 },
|
||||
{ month: 'May', desktop: 209, mobile: 130 },
|
||||
{ month: 'June', desktop: 214, mobile: 140 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
mobile: {
|
||||
label: 'Mobile',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Area Chart - Legend</CardTitle>
|
||||
<CardDescription>Showing total visitors for the last 6 months</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<AreaChart
|
||||
accessibilityLayer
|
||||
data={chartData}
|
||||
margin={{
|
||||
left: 12,
|
||||
right: 12,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="month"
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
tickFormatter={(value) => value.slice(0, 3)}
|
||||
/>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent indicator="line" />} />
|
||||
<Area
|
||||
dataKey="mobile"
|
||||
type="natural"
|
||||
fill="var(--color-mobile)"
|
||||
fillOpacity={0.4}
|
||||
stroke="var(--color-mobile)"
|
||||
stackId="a"
|
||||
/>
|
||||
<Area
|
||||
dataKey="desktop"
|
||||
type="natural"
|
||||
fill="var(--color-desktop)"
|
||||
fillOpacity={0.4}
|
||||
stroke="var(--color-desktop)"
|
||||
stackId="a"
|
||||
/>
|
||||
<ChartLegend content={<ChartLegendContent />} />
|
||||
</AreaChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<div className="flex w-full items-start gap-2 text-sm">
|
||||
<div className="grid gap-2">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2 leading-none text-muted-foreground">
|
||||
January - June 2024
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { Area, AreaChart, CartesianGrid, XAxis } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A linear area chart'
|
||||
|
||||
const chartData = [
|
||||
{ month: 'January', desktop: 186 },
|
||||
{ month: 'February', desktop: 305 },
|
||||
{ month: 'March', desktop: 237 },
|
||||
{ month: 'April', desktop: 73 },
|
||||
{ month: 'May', desktop: 209 },
|
||||
{ month: 'June', desktop: 214 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Area Chart - Linear</CardTitle>
|
||||
<CardDescription>Showing total visitors for the last 6 months</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<AreaChart
|
||||
accessibilityLayer
|
||||
data={chartData}
|
||||
margin={{
|
||||
left: 12,
|
||||
right: 12,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="month"
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
tickFormatter={(value) => value.slice(0, 3)}
|
||||
/>
|
||||
<ChartTooltip
|
||||
cursor={false}
|
||||
content={<ChartTooltipContent indicator="dot" hideLabel />}
|
||||
/>
|
||||
<Area
|
||||
dataKey="desktop"
|
||||
type="linear"
|
||||
fill="var(--color-desktop)"
|
||||
fillOpacity={0.4}
|
||||
stroke="var(--color-desktop)"
|
||||
/>
|
||||
</AreaChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<div className="flex w-full items-start gap-2 text-sm">
|
||||
<div className="grid gap-2">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2 leading-none text-muted-foreground">
|
||||
January - June 2024
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { Area, AreaChart, CartesianGrid, XAxis } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A stacked area chart with expand stacking'
|
||||
|
||||
const chartData = [
|
||||
{ month: 'January', desktop: 186, mobile: 80, other: 45 },
|
||||
{ month: 'February', desktop: 305, mobile: 200, other: 100 },
|
||||
{ month: 'March', desktop: 237, mobile: 120, other: 150 },
|
||||
{ month: 'April', desktop: 73, mobile: 190, other: 50 },
|
||||
{ month: 'May', desktop: 209, mobile: 130, other: 100 },
|
||||
{ month: 'June', desktop: 214, mobile: 140, other: 160 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
mobile: {
|
||||
label: 'Mobile',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
other: {
|
||||
label: 'Other',
|
||||
color: 'hsl(var(--chart-3))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Area Chart - Stacked Expanded</CardTitle>
|
||||
<CardDescription>Showing total visitors for the last 6months</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<AreaChart
|
||||
accessibilityLayer
|
||||
data={chartData}
|
||||
margin={{
|
||||
left: 12,
|
||||
right: 12,
|
||||
top: 12,
|
||||
}}
|
||||
stackOffset="expand"
|
||||
>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="month"
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
tickFormatter={(value) => value.slice(0, 3)}
|
||||
/>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent indicator="line" />} />
|
||||
<Area
|
||||
dataKey="other"
|
||||
type="natural"
|
||||
fill="var(--color-other)"
|
||||
fillOpacity={0.1}
|
||||
stroke="var(--color-other)"
|
||||
stackId="a"
|
||||
/>
|
||||
<Area
|
||||
dataKey="mobile"
|
||||
type="natural"
|
||||
fill="var(--color-mobile)"
|
||||
fillOpacity={0.4}
|
||||
stroke="var(--color-mobile)"
|
||||
stackId="a"
|
||||
/>
|
||||
<Area
|
||||
dataKey="desktop"
|
||||
type="natural"
|
||||
fill="var(--color-desktop)"
|
||||
fillOpacity={0.4}
|
||||
stroke="var(--color-desktop)"
|
||||
stackId="a"
|
||||
/>
|
||||
</AreaChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<div className="flex w-full items-start gap-2 text-sm">
|
||||
<div className="grid gap-2">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2 leading-none text-muted-foreground">
|
||||
January - June 2024
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { Area, AreaChart, CartesianGrid, XAxis } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A stacked area chart'
|
||||
|
||||
const chartData = [
|
||||
{ month: 'January', desktop: 186, mobile: 80 },
|
||||
{ month: 'February', desktop: 305, mobile: 200 },
|
||||
{ month: 'March', desktop: 237, mobile: 120 },
|
||||
{ month: 'April', desktop: 73, mobile: 190 },
|
||||
{ month: 'May', desktop: 209, mobile: 130 },
|
||||
{ month: 'June', desktop: 214, mobile: 140 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
mobile: {
|
||||
label: 'Mobile',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Area Chart - Stacked</CardTitle>
|
||||
<CardDescription>Showing total visitors for the last 6 months</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<AreaChart
|
||||
accessibilityLayer
|
||||
data={chartData}
|
||||
margin={{
|
||||
left: 12,
|
||||
right: 12,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="month"
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
tickFormatter={(value) => value.slice(0, 3)}
|
||||
/>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent indicator="dot" />} />
|
||||
<Area
|
||||
dataKey="mobile"
|
||||
type="natural"
|
||||
fill="var(--color-mobile)"
|
||||
fillOpacity={0.4}
|
||||
stroke="var(--color-mobile)"
|
||||
stackId="a"
|
||||
/>
|
||||
<Area
|
||||
dataKey="desktop"
|
||||
type="natural"
|
||||
fill="var(--color-desktop)"
|
||||
fillOpacity={0.4}
|
||||
stroke="var(--color-desktop)"
|
||||
stackId="a"
|
||||
/>
|
||||
</AreaChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<div className="flex w-full items-start gap-2 text-sm">
|
||||
<div className="grid gap-2">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2 leading-none text-muted-foreground">
|
||||
January - June 2024
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { Activity, TrendingUp } from 'lucide-react'
|
||||
import { Area, AreaChart, CartesianGrid, XAxis } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A step area chart'
|
||||
|
||||
const chartData = [
|
||||
{ month: 'January', desktop: 186 },
|
||||
{ month: 'February', desktop: 305 },
|
||||
{ month: 'March', desktop: 237 },
|
||||
{ month: 'April', desktop: 73 },
|
||||
{ month: 'May', desktop: 209 },
|
||||
{ month: 'June', desktop: 214 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
icon: Activity,
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Area Chart - Step</CardTitle>
|
||||
<CardDescription>Showing total visitors for the last 6 months</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<AreaChart
|
||||
accessibilityLayer
|
||||
data={chartData}
|
||||
margin={{
|
||||
left: 12,
|
||||
right: 12,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="month"
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
tickFormatter={(value) => value.slice(0, 3)}
|
||||
/>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
|
||||
<Area
|
||||
dataKey="desktop"
|
||||
type="step"
|
||||
fill="var(--color-desktop)"
|
||||
fillOpacity={0.4}
|
||||
stroke="var(--color-desktop)"
|
||||
/>
|
||||
</AreaChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<div className="flex w-full items-start gap-2 text-sm">
|
||||
<div className="grid gap-2">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2 leading-none text-muted-foreground">
|
||||
January - June 2024
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { Bar, BarChart, CartesianGrid, Rectangle, XAxis } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A bar chart with an active bar'
|
||||
|
||||
const chartData = [
|
||||
{ browser: 'chrome', visitors: 187, fill: 'var(--color-chrome)' },
|
||||
{ browser: 'safari', visitors: 200, fill: 'var(--color-safari)' },
|
||||
{ browser: 'firefox', visitors: 275, fill: 'var(--color-firefox)' },
|
||||
{ browser: 'edge', visitors: 173, fill: 'var(--color-edge)' },
|
||||
{ browser: 'other', visitors: 90, fill: 'var(--color-other)' },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
visitors: {
|
||||
label: 'Visitors',
|
||||
},
|
||||
chrome: {
|
||||
label: 'Chrome',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
safari: {
|
||||
label: 'Safari',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
firefox: {
|
||||
label: 'Firefox',
|
||||
color: 'hsl(var(--chart-3))',
|
||||
},
|
||||
edge: {
|
||||
label: 'Edge',
|
||||
color: 'hsl(var(--chart-4))',
|
||||
},
|
||||
other: {
|
||||
label: 'Other',
|
||||
color: 'hsl(var(--chart-5))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Bar Chart - Active</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<BarChart accessibilityLayer data={chartData}>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="browser"
|
||||
tickLine={false}
|
||||
tickMargin={10}
|
||||
axisLine={false}
|
||||
tickFormatter={(value) => chartConfig[value as keyof typeof chartConfig]?.label}
|
||||
/>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
|
||||
<Bar
|
||||
dataKey="visitors"
|
||||
strokeWidth={2}
|
||||
radius={8}
|
||||
activeIndex={2}
|
||||
activeBar={({ ...props }) => {
|
||||
return (
|
||||
<Rectangle
|
||||
{...props}
|
||||
fillOpacity={0.8}
|
||||
stroke={props.payload.fill}
|
||||
strokeDasharray={4}
|
||||
strokeDashoffset={4}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</BarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col items-start gap-2 text-sm">
|
||||
<div className="flex gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { Bar, BarChart, CartesianGrid, XAxis } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A bar chart'
|
||||
|
||||
const chartData = [
|
||||
{ month: 'January', desktop: 186 },
|
||||
{ month: 'February', desktop: 305 },
|
||||
{ month: 'March', desktop: 237 },
|
||||
{ month: 'April', desktop: 73 },
|
||||
{ month: 'May', desktop: 209 },
|
||||
{ month: 'June', desktop: 214 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Bar Chart</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<BarChart accessibilityLayer data={chartData}>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="month"
|
||||
tickLine={false}
|
||||
tickMargin={10}
|
||||
axisLine={false}
|
||||
tickFormatter={(value) => value.slice(0, 3)}
|
||||
/>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
|
||||
<Bar dataKey="desktop" fill="var(--color-desktop)" radius={8} />
|
||||
</BarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col items-start gap-2 text-sm">
|
||||
<div className="flex gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { Bar, BarChart, XAxis, YAxis } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A horizontal bar chart'
|
||||
|
||||
const chartData = [
|
||||
{ month: 'January', desktop: 186 },
|
||||
{ month: 'February', desktop: 305 },
|
||||
{ month: 'March', desktop: 237 },
|
||||
{ month: 'April', desktop: 73 },
|
||||
{ month: 'May', desktop: 209 },
|
||||
{ month: 'June', desktop: 214 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Bar Chart - Horizontal</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<BarChart
|
||||
accessibilityLayer
|
||||
data={chartData}
|
||||
layout="vertical"
|
||||
margin={{
|
||||
left: -20,
|
||||
}}
|
||||
>
|
||||
<XAxis type="number" dataKey="desktop" hide />
|
||||
<YAxis
|
||||
dataKey="month"
|
||||
type="category"
|
||||
tickLine={false}
|
||||
tickMargin={10}
|
||||
axisLine={false}
|
||||
tickFormatter={(value) => value.slice(0, 3)}
|
||||
/>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
|
||||
<Bar dataKey="desktop" fill="var(--color-desktop)" radius={5} />
|
||||
</BarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col items-start gap-2 text-sm">
|
||||
<div className="flex gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { Bar, BarChart, CartesianGrid, LabelList, XAxis, YAxis } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A bar chart with a custom label'
|
||||
|
||||
const chartData = [
|
||||
{ month: 'January', desktop: 186, mobile: 80 },
|
||||
{ month: 'February', desktop: 305, mobile: 200 },
|
||||
{ month: 'March', desktop: 237, mobile: 120 },
|
||||
{ month: 'April', desktop: 73, mobile: 190 },
|
||||
{ month: 'May', desktop: 209, mobile: 130 },
|
||||
{ month: 'June', desktop: 214, mobile: 140 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
mobile: {
|
||||
label: 'Mobile',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
label: {
|
||||
color: 'hsl(var(--background))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Bar Chart - Custom Label</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<BarChart
|
||||
accessibilityLayer
|
||||
data={chartData}
|
||||
layout="vertical"
|
||||
margin={{
|
||||
right: 16,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid horizontal={false} />
|
||||
<YAxis
|
||||
dataKey="month"
|
||||
type="category"
|
||||
tickLine={false}
|
||||
tickMargin={10}
|
||||
axisLine={false}
|
||||
tickFormatter={(value) => value.slice(0, 3)}
|
||||
hide
|
||||
/>
|
||||
<XAxis dataKey="desktop" type="number" hide />
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent indicator="line" />} />
|
||||
<Bar dataKey="desktop" layout="vertical" fill="var(--color-desktop)" radius={4}>
|
||||
<LabelList
|
||||
dataKey="month"
|
||||
position="insideLeft"
|
||||
offset={8}
|
||||
className="fill-[--color-label]"
|
||||
fontSize={12}
|
||||
/>
|
||||
<LabelList
|
||||
dataKey="desktop"
|
||||
position="right"
|
||||
offset={8}
|
||||
className="fill-foreground"
|
||||
fontSize={12}
|
||||
/>
|
||||
</Bar>
|
||||
</BarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col items-start gap-2 text-sm">
|
||||
<div className="flex gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { Bar, BarChart, CartesianGrid, LabelList, XAxis } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A bar chart with a label'
|
||||
|
||||
const chartData = [
|
||||
{ month: 'January', desktop: 186 },
|
||||
{ month: 'February', desktop: 305 },
|
||||
{ month: 'March', desktop: 237 },
|
||||
{ month: 'April', desktop: 73 },
|
||||
{ month: 'May', desktop: 209 },
|
||||
{ month: 'June', desktop: 214 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Bar Chart - Label</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<BarChart
|
||||
accessibilityLayer
|
||||
data={chartData}
|
||||
margin={{
|
||||
top: 20,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="month"
|
||||
tickLine={false}
|
||||
tickMargin={10}
|
||||
axisLine={false}
|
||||
tickFormatter={(value) => value.slice(0, 3)}
|
||||
/>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
|
||||
<Bar dataKey="desktop" fill="var(--color-desktop)" radius={8}>
|
||||
<LabelList position="top" offset={12} className="fill-foreground" fontSize={12} />
|
||||
</Bar>
|
||||
</BarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col items-start gap-2 text-sm">
|
||||
<div className="flex gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { Bar, BarChart, XAxis, YAxis } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A mixed bar chart'
|
||||
|
||||
const chartData = [
|
||||
{ browser: 'chrome', visitors: 275, fill: 'var(--color-chrome)' },
|
||||
{ browser: 'safari', visitors: 200, fill: 'var(--color-safari)' },
|
||||
{ browser: 'firefox', visitors: 187, fill: 'var(--color-firefox)' },
|
||||
{ browser: 'edge', visitors: 173, fill: 'var(--color-edge)' },
|
||||
{ browser: 'other', visitors: 90, fill: 'var(--color-other)' },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
visitors: {
|
||||
label: 'Visitors',
|
||||
},
|
||||
chrome: {
|
||||
label: 'Chrome',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
safari: {
|
||||
label: 'Safari',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
firefox: {
|
||||
label: 'Firefox',
|
||||
color: 'hsl(var(--chart-3))',
|
||||
},
|
||||
edge: {
|
||||
label: 'Edge',
|
||||
color: 'hsl(var(--chart-4))',
|
||||
},
|
||||
other: {
|
||||
label: 'Other',
|
||||
color: 'hsl(var(--chart-5))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Bar Chart - Mixed</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<BarChart
|
||||
accessibilityLayer
|
||||
data={chartData}
|
||||
layout="vertical"
|
||||
margin={{
|
||||
left: 0,
|
||||
}}
|
||||
>
|
||||
<YAxis
|
||||
dataKey="browser"
|
||||
type="category"
|
||||
tickLine={false}
|
||||
tickMargin={10}
|
||||
axisLine={false}
|
||||
tickFormatter={(value) => chartConfig[value as keyof typeof chartConfig]?.label}
|
||||
/>
|
||||
<XAxis dataKey="visitors" type="number" hide />
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
|
||||
<Bar dataKey="visitors" layout="vertical" radius={5} />
|
||||
</BarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col items-start gap-2 text-sm">
|
||||
<div className="flex gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { Bar, BarChart, CartesianGrid, XAxis } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A multiple bar chart'
|
||||
|
||||
const chartData = [
|
||||
{ month: 'January', desktop: 186, mobile: 80 },
|
||||
{ month: 'February', desktop: 305, mobile: 200 },
|
||||
{ month: 'March', desktop: 237, mobile: 120 },
|
||||
{ month: 'April', desktop: 73, mobile: 190 },
|
||||
{ month: 'May', desktop: 209, mobile: 130 },
|
||||
{ month: 'June', desktop: 214, mobile: 140 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
mobile: {
|
||||
label: 'Mobile',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Bar Chart - Multiple</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<BarChart accessibilityLayer data={chartData}>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="month"
|
||||
tickLine={false}
|
||||
tickMargin={10}
|
||||
axisLine={false}
|
||||
tickFormatter={(value) => value.slice(0, 3)}
|
||||
/>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent indicator="dashed" />} />
|
||||
<Bar dataKey="desktop" fill="var(--color-desktop)" radius={4} />
|
||||
<Bar dataKey="mobile" fill="var(--color-mobile)" radius={4} />
|
||||
</BarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col items-start gap-2 text-sm">
|
||||
<div className="flex gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { Bar, BarChart, CartesianGrid, Cell, LabelList } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A bar chart with negative values'
|
||||
|
||||
const chartData = [
|
||||
{ month: 'January', visitors: 186 },
|
||||
{ month: 'February', visitors: 205 },
|
||||
{ month: 'March', visitors: -207 },
|
||||
{ month: 'April', visitors: 173 },
|
||||
{ month: 'May', visitors: -209 },
|
||||
{ month: 'June', visitors: 214 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
visitors: {
|
||||
label: 'Visitors',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Bar Chart - Negative</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<BarChart accessibilityLayer data={chartData}>
|
||||
<CartesianGrid vertical={false} />
|
||||
<ChartTooltip
|
||||
cursor={false}
|
||||
content={<ChartTooltipContent hideLabel hideIndicator />}
|
||||
/>
|
||||
<Bar dataKey="visitors">
|
||||
<LabelList position="top" dataKey="month" fillOpacity={1} />
|
||||
{chartData.map((item) => (
|
||||
<Cell
|
||||
key={item.month}
|
||||
fill={item.visitors > 0 ? 'hsl(var(--chart-1))' : 'hsl(var(--chart-2))'}
|
||||
/>
|
||||
))}
|
||||
</Bar>
|
||||
</BarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col items-start gap-2 text-sm">
|
||||
<div className="flex gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { Bar, BarChart, CartesianGrid, XAxis } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import {
|
||||
ChartConfig,
|
||||
ChartContainer,
|
||||
ChartLegend,
|
||||
ChartLegendContent,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
} from 'ui'
|
||||
|
||||
export const description = 'A stacked bar chart with a legend'
|
||||
|
||||
const chartData = [
|
||||
{ month: 'January', desktop: 186, mobile: 80 },
|
||||
{ month: 'February', desktop: 305, mobile: 200 },
|
||||
{ month: 'March', desktop: 237, mobile: 120 },
|
||||
{ month: 'April', desktop: 73, mobile: 190 },
|
||||
{ month: 'May', desktop: 209, mobile: 130 },
|
||||
{ month: 'June', desktop: 214, mobile: 140 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
mobile: {
|
||||
label: 'Mobile',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Bar Chart - Stacked + Legend</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<BarChart accessibilityLayer data={chartData}>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="month"
|
||||
tickLine={false}
|
||||
tickMargin={10}
|
||||
axisLine={false}
|
||||
tickFormatter={(value) => value.slice(0, 3)}
|
||||
/>
|
||||
<ChartTooltip content={<ChartTooltipContent hideLabel />} />
|
||||
<ChartLegend content={<ChartLegendContent />} />
|
||||
<Bar dataKey="desktop" stackId="a" fill="var(--color-desktop)" radius={[0, 0, 4, 4]} />
|
||||
<Bar dataKey="mobile" stackId="a" fill="var(--color-mobile)" radius={[4, 4, 0, 0]} />
|
||||
</BarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col items-start gap-2 text-sm">
|
||||
<div className="flex gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
'use client'
|
||||
|
||||
import {
|
||||
Chart,
|
||||
ChartActions,
|
||||
ChartCard,
|
||||
ChartContent,
|
||||
ChartHeader,
|
||||
ChartTitle,
|
||||
ChartEmptyState,
|
||||
ChartLoadingState,
|
||||
ChartBar,
|
||||
ChartLine,
|
||||
} from 'ui-patterns/Chart'
|
||||
import { BarChart2, ExternalLink, LineChart } from 'lucide-react'
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
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>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
'use client'
|
||||
|
||||
import {
|
||||
Chart,
|
||||
ChartActions,
|
||||
ChartCard,
|
||||
ChartContent,
|
||||
ChartHeader,
|
||||
ChartTitle,
|
||||
ChartEmptyState,
|
||||
ChartLoadingState,
|
||||
ChartBar,
|
||||
ChartLine,
|
||||
} from 'ui-patterns/Chart'
|
||||
import { BarChart2, ExternalLink } from 'lucide-react'
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
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: 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>
|
||||
</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"
|
||||
showGrid={true}
|
||||
showYAxis={true}
|
||||
YAxisProps={{
|
||||
tickFormatter: (value) => `${value}k`,
|
||||
width: 80,
|
||||
}}
|
||||
isFullHeight={true}
|
||||
/>
|
||||
</div>
|
||||
</ChartContent>
|
||||
</ChartCard>
|
||||
</Chart>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
'use client'
|
||||
|
||||
import {
|
||||
Chart,
|
||||
ChartCard,
|
||||
ChartContent,
|
||||
ChartHeader,
|
||||
ChartEmptyState,
|
||||
ChartLoadingState,
|
||||
ChartMetric,
|
||||
} from 'ui-patterns/Chart'
|
||||
import { BarChart2 } from 'lucide-react'
|
||||
import { LogsBarChart } from 'ui-patterns/LogsBarChart'
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
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>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
'use client'
|
||||
|
||||
import {
|
||||
Chart,
|
||||
ChartCard,
|
||||
ChartHeader,
|
||||
ChartActions,
|
||||
ChartContent,
|
||||
ChartMetric,
|
||||
ChartSparkline,
|
||||
} from 'ui-patterns/Chart'
|
||||
import { ExternalLink } from 'lucide-react'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Skeleton } from 'ui'
|
||||
|
||||
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-[6rem] 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-[6rem] rounded-none mt-4" />}
|
||||
>
|
||||
<ChartSparkline data={data} dataKey="value" />
|
||||
</ChartContent>
|
||||
</ChartCard>
|
||||
</Chart>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
'use client'
|
||||
|
||||
import { BarChart2, ExternalLink } from 'lucide-react'
|
||||
import {
|
||||
Chart,
|
||||
ChartCard,
|
||||
ChartHeader,
|
||||
ChartTitle,
|
||||
ChartActions,
|
||||
ChartContent,
|
||||
ChartEmptyState,
|
||||
ChartLoadingState,
|
||||
ChartDisabledState,
|
||||
} from 'ui-patterns/Chart'
|
||||
import { Badge } from 'ui'
|
||||
|
||||
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>
|
||||
<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>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
'use client'
|
||||
|
||||
import {
|
||||
Chart,
|
||||
ChartActions,
|
||||
ChartCard,
|
||||
ChartContent,
|
||||
ChartHeader,
|
||||
ChartTitle,
|
||||
ChartEmptyState,
|
||||
ChartLoadingState,
|
||||
ChartBar,
|
||||
ChartFooter,
|
||||
} from 'ui-patterns/Chart'
|
||||
import { BarChart2, ExternalLink } from 'lucide-react'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Table, TableHead, TableBody, TableRow, TableCell, TableHeader } from 'ui'
|
||||
import { format } from 'date-fns'
|
||||
|
||||
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>
|
||||
)
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { CartesianGrid, Line, LineChart, XAxis } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A line chart'
|
||||
|
||||
const chartData = [
|
||||
{ month: 'January', desktop: 186 },
|
||||
{ month: 'February', desktop: 305 },
|
||||
{ month: 'March', desktop: 237 },
|
||||
{ month: 'April', desktop: 73 },
|
||||
{ month: 'May', desktop: 209 },
|
||||
{ month: 'June', desktop: 214 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Line Chart</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<LineChart
|
||||
accessibilityLayer
|
||||
data={chartData}
|
||||
margin={{
|
||||
left: 12,
|
||||
right: 12,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="month"
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
tickFormatter={(value) => value.slice(0, 3)}
|
||||
/>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
|
||||
<Line
|
||||
dataKey="desktop"
|
||||
type="natural"
|
||||
stroke="var(--color-desktop)"
|
||||
strokeWidth={2}
|
||||
dot={false}
|
||||
/>
|
||||
</LineChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col items-start gap-2 text-sm">
|
||||
<div className="flex gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { CartesianGrid, Dot, Line, LineChart } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A line chart with dots and colors'
|
||||
|
||||
const chartData = [
|
||||
{ browser: 'chrome', visitors: 275, fill: 'var(--color-chrome)' },
|
||||
{ browser: 'safari', visitors: 200, fill: 'var(--color-safari)' },
|
||||
{ browser: 'firefox', visitors: 187, fill: 'var(--color-firefox)' },
|
||||
{ browser: 'edge', visitors: 173, fill: 'var(--color-edge)' },
|
||||
{ browser: 'other', visitors: 90, fill: 'var(--color-other)' },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
visitors: {
|
||||
label: 'Visitors',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
chrome: {
|
||||
label: 'Chrome',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
safari: {
|
||||
label: 'Safari',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
firefox: {
|
||||
label: 'Firefox',
|
||||
color: 'hsl(var(--chart-3))',
|
||||
},
|
||||
edge: {
|
||||
label: 'Edge',
|
||||
color: 'hsl(var(--chart-4))',
|
||||
},
|
||||
other: {
|
||||
label: 'Other',
|
||||
color: 'hsl(var(--chart-5))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Line Chart - Dots Colors</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<LineChart
|
||||
accessibilityLayer
|
||||
data={chartData}
|
||||
margin={{
|
||||
top: 24,
|
||||
left: 24,
|
||||
right: 24,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid vertical={false} />
|
||||
<ChartTooltip
|
||||
cursor={false}
|
||||
content={<ChartTooltipContent indicator="line" nameKey="visitors" hideLabel />}
|
||||
/>
|
||||
<Line
|
||||
dataKey="visitors"
|
||||
type="natural"
|
||||
stroke="var(--color-visitors)"
|
||||
strokeWidth={2}
|
||||
dot={({ payload, ...props }) => {
|
||||
return (
|
||||
<Dot
|
||||
key={payload.browser}
|
||||
r={5}
|
||||
cx={props.cx}
|
||||
cy={props.cy}
|
||||
fill={payload.fill}
|
||||
stroke={payload.fill}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</LineChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col items-start gap-2 text-sm">
|
||||
<div className="flex gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { GitCommitVertical, TrendingUp } from 'lucide-react'
|
||||
import { CartesianGrid, Line, LineChart, XAxis } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A line chart with custom dots'
|
||||
|
||||
const chartData = [
|
||||
{ month: 'January', desktop: 186, mobile: 80 },
|
||||
{ month: 'February', desktop: 305, mobile: 200 },
|
||||
{ month: 'March', desktop: 237, mobile: 120 },
|
||||
{ month: 'April', desktop: 73, mobile: 190 },
|
||||
{ month: 'May', desktop: 209, mobile: 130 },
|
||||
{ month: 'June', desktop: 214, mobile: 140 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
mobile: {
|
||||
label: 'Mobile',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Line Chart - Custom Dots</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<LineChart
|
||||
accessibilityLayer
|
||||
data={chartData}
|
||||
margin={{
|
||||
left: 12,
|
||||
right: 12,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="month"
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
tickFormatter={(value) => value.slice(0, 3)}
|
||||
/>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
|
||||
<Line
|
||||
dataKey="desktop"
|
||||
type="natural"
|
||||
stroke="var(--color-desktop)"
|
||||
strokeWidth={2}
|
||||
dot={({ cx, cy, payload }) => {
|
||||
const r = 24
|
||||
return (
|
||||
<GitCommitVertical
|
||||
key={payload.month}
|
||||
x={cx - r / 2}
|
||||
y={cy - r / 2}
|
||||
width={r}
|
||||
height={r}
|
||||
fill="hsl(var(--background))"
|
||||
stroke="var(--color-desktop)"
|
||||
/>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</LineChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col items-start gap-2 text-sm">
|
||||
<div className="flex gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { CartesianGrid, Line, LineChart, XAxis } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A line chart with dots'
|
||||
|
||||
const chartData = [
|
||||
{ month: 'January', desktop: 186, mobile: 80 },
|
||||
{ month: 'February', desktop: 305, mobile: 200 },
|
||||
{ month: 'March', desktop: 237, mobile: 120 },
|
||||
{ month: 'April', desktop: 73, mobile: 190 },
|
||||
{ month: 'May', desktop: 209, mobile: 130 },
|
||||
{ month: 'June', desktop: 214, mobile: 140 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
mobile: {
|
||||
label: 'Mobile',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Line Chart - Dots</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<LineChart
|
||||
accessibilityLayer
|
||||
data={chartData}
|
||||
margin={{
|
||||
left: 12,
|
||||
right: 12,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="month"
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
tickFormatter={(value) => value.slice(0, 3)}
|
||||
/>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
|
||||
<Line
|
||||
dataKey="desktop"
|
||||
type="natural"
|
||||
stroke="var(--color-desktop)"
|
||||
strokeWidth={2}
|
||||
dot={{
|
||||
fill: 'var(--color-desktop)',
|
||||
}}
|
||||
activeDot={{
|
||||
r: 6,
|
||||
}}
|
||||
/>
|
||||
</LineChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col items-start gap-2 text-sm">
|
||||
<div className="flex gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,208 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import { CartesianGrid, Line, LineChart, XAxis } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'An interactive line 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>Line 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
|
||||
key={chart}
|
||||
data-active={activeChart === chart}
|
||||
className="flex flex-1 flex-col justify-center gap-1 border-t px-6 py-4 text-left even:border-l data-[active=true]:bg-muted/50 sm:border-l sm:border-t-0 sm:px-8 sm:py-6"
|
||||
onClick={() => setActiveChart(chart)}
|
||||
>
|
||||
<span className="text-xs text-muted-foreground">{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">
|
||||
<LineChart
|
||||
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',
|
||||
})
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Line
|
||||
dataKey={activeChart}
|
||||
type="monotone"
|
||||
stroke={`var(--color-${activeChart})`}
|
||||
strokeWidth={2}
|
||||
dot={false}
|
||||
/>
|
||||
</LineChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { CartesianGrid, LabelList, Line, LineChart } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A line chart with a custom label'
|
||||
|
||||
const chartData = [
|
||||
{ browser: 'chrome', visitors: 275, fill: 'var(--color-chrome)' },
|
||||
{ browser: 'safari', visitors: 200, fill: 'var(--color-safari)' },
|
||||
{ browser: 'firefox', visitors: 187, fill: 'var(--color-firefox)' },
|
||||
{ browser: 'edge', visitors: 173, fill: 'var(--color-edge)' },
|
||||
{ browser: 'other', visitors: 90, fill: 'var(--color-other)' },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
visitors: {
|
||||
label: 'Visitors',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
chrome: {
|
||||
label: 'Chrome',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
safari: {
|
||||
label: 'Safari',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
firefox: {
|
||||
label: 'Firefox',
|
||||
color: 'hsl(var(--chart-3))',
|
||||
},
|
||||
edge: {
|
||||
label: 'Edge',
|
||||
color: 'hsl(var(--chart-4))',
|
||||
},
|
||||
other: {
|
||||
label: 'Other',
|
||||
color: 'hsl(var(--chart-5))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Line Chart - Custom Label</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<LineChart
|
||||
accessibilityLayer
|
||||
data={chartData}
|
||||
margin={{
|
||||
top: 24,
|
||||
left: 24,
|
||||
right: 24,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid vertical={false} />
|
||||
<ChartTooltip
|
||||
cursor={false}
|
||||
content={<ChartTooltipContent indicator="line" nameKey="visitors" hideLabel />}
|
||||
/>
|
||||
<Line
|
||||
dataKey="visitors"
|
||||
type="natural"
|
||||
stroke="var(--color-visitors)"
|
||||
strokeWidth={2}
|
||||
dot={{
|
||||
fill: 'var(--color-visitors)',
|
||||
}}
|
||||
activeDot={{
|
||||
r: 6,
|
||||
}}
|
||||
>
|
||||
<LabelList
|
||||
position="top"
|
||||
offset={12}
|
||||
className="fill-foreground"
|
||||
fontSize={12}
|
||||
dataKey="browser"
|
||||
formatter={(value: keyof typeof chartConfig) => chartConfig[value]?.label}
|
||||
/>
|
||||
</Line>
|
||||
</LineChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col items-start gap-2 text-sm">
|
||||
<div className="flex gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { CartesianGrid, LabelList, Line, LineChart, XAxis } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A line chart with a label'
|
||||
|
||||
const chartData = [
|
||||
{ month: 'January', desktop: 186, mobile: 80 },
|
||||
{ month: 'February', desktop: 305, mobile: 200 },
|
||||
{ month: 'March', desktop: 237, mobile: 120 },
|
||||
{ month: 'April', desktop: 73, mobile: 190 },
|
||||
{ month: 'May', desktop: 209, mobile: 130 },
|
||||
{ month: 'June', desktop: 214, mobile: 140 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
mobile: {
|
||||
label: 'Mobile',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Line Chart - Label</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<LineChart
|
||||
accessibilityLayer
|
||||
data={chartData}
|
||||
margin={{
|
||||
top: 20,
|
||||
left: 12,
|
||||
right: 12,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="month"
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
tickFormatter={(value) => value.slice(0, 3)}
|
||||
/>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent indicator="line" />} />
|
||||
<Line
|
||||
dataKey="desktop"
|
||||
type="natural"
|
||||
stroke="var(--color-desktop)"
|
||||
strokeWidth={2}
|
||||
dot={{
|
||||
fill: 'var(--color-desktop)',
|
||||
}}
|
||||
activeDot={{
|
||||
r: 6,
|
||||
}}
|
||||
>
|
||||
<LabelList position="top" offset={12} className="fill-foreground" fontSize={12} />
|
||||
</Line>
|
||||
</LineChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col items-start gap-2 text-sm">
|
||||
<div className="flex gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { CartesianGrid, Line, LineChart, XAxis } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A linear line chart'
|
||||
|
||||
const chartData = [
|
||||
{ month: 'January', desktop: 186 },
|
||||
{ month: 'February', desktop: 305 },
|
||||
{ month: 'March', desktop: 237 },
|
||||
{ month: 'April', desktop: 73 },
|
||||
{ month: 'May', desktop: 209 },
|
||||
{ month: 'June', desktop: 214 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Line Chart - Linear</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<LineChart
|
||||
accessibilityLayer
|
||||
data={chartData}
|
||||
margin={{
|
||||
left: 12,
|
||||
right: 12,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="month"
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
tickFormatter={(value) => value.slice(0, 3)}
|
||||
/>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
|
||||
<Line
|
||||
dataKey="desktop"
|
||||
type="linear"
|
||||
stroke="var(--color-desktop)"
|
||||
strokeWidth={2}
|
||||
dot={false}
|
||||
/>
|
||||
</LineChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col items-start gap-2 text-sm">
|
||||
<div className="flex gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { CartesianGrid, Line, LineChart, XAxis } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A multiple line chart'
|
||||
|
||||
const chartData = [
|
||||
{ month: 'January', desktop: 186, mobile: 80 },
|
||||
{ month: 'February', desktop: 305, mobile: 200 },
|
||||
{ month: 'March', desktop: 237, mobile: 120 },
|
||||
{ month: 'April', desktop: 73, mobile: 190 },
|
||||
{ month: 'May', desktop: 209, mobile: 130 },
|
||||
{ month: 'June', desktop: 214, mobile: 140 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
mobile: {
|
||||
label: 'Mobile',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Line Chart - Multiple</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<LineChart
|
||||
accessibilityLayer
|
||||
data={chartData}
|
||||
margin={{
|
||||
left: 12,
|
||||
right: 12,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="month"
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
tickFormatter={(value) => value.slice(0, 3)}
|
||||
/>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent />} />
|
||||
<Line
|
||||
dataKey="desktop"
|
||||
type="monotone"
|
||||
stroke="var(--color-desktop)"
|
||||
strokeWidth={2}
|
||||
dot={false}
|
||||
/>
|
||||
<Line
|
||||
dataKey="mobile"
|
||||
type="monotone"
|
||||
stroke="var(--color-mobile)"
|
||||
strokeWidth={2}
|
||||
dot={false}
|
||||
/>
|
||||
</LineChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<div className="flex w-full items-start gap-2 text-sm">
|
||||
<div className="grid gap-2">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2 leading-none text-muted-foreground">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { CartesianGrid, Line, LineChart, XAxis } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A line chart with step'
|
||||
|
||||
const chartData = [
|
||||
{ month: 'January', desktop: 186 },
|
||||
{ month: 'February', desktop: 305 },
|
||||
{ month: 'March', desktop: 237 },
|
||||
{ month: 'April', desktop: 73 },
|
||||
{ month: 'May', desktop: 209 },
|
||||
{ month: 'June', desktop: 214 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Line Chart - Step</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<LineChart
|
||||
accessibilityLayer
|
||||
data={chartData}
|
||||
margin={{
|
||||
left: 12,
|
||||
right: 12,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="month"
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
tickFormatter={(value) => value.slice(0, 3)}
|
||||
/>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
|
||||
<Line
|
||||
dataKey="desktop"
|
||||
type="step"
|
||||
stroke="var(--color-desktop)"
|
||||
strokeWidth={2}
|
||||
dot={false}
|
||||
/>
|
||||
</LineChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col items-start gap-2 text-sm">
|
||||
<div className="flex gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { Label, Pie, PieChart, Sector } from 'recharts'
|
||||
import { PieSectorDataItem } from 'recharts/types/polar/Pie'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A donut chart with an active sector'
|
||||
|
||||
const chartData = [
|
||||
{ browser: 'chrome', visitors: 275, fill: 'var(--color-chrome)' },
|
||||
{ browser: 'safari', visitors: 200, fill: 'var(--color-safari)' },
|
||||
{ browser: 'firefox', visitors: 187, fill: 'var(--color-firefox)' },
|
||||
{ browser: 'edge', visitors: 173, fill: 'var(--color-edge)' },
|
||||
{ browser: 'other', visitors: 90, fill: 'var(--color-other)' },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
visitors: {
|
||||
label: 'Visitors',
|
||||
},
|
||||
chrome: {
|
||||
label: 'Chrome',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
safari: {
|
||||
label: 'Safari',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
firefox: {
|
||||
label: 'Firefox',
|
||||
color: 'hsl(var(--chart-3))',
|
||||
},
|
||||
edge: {
|
||||
label: 'Edge',
|
||||
color: 'hsl(var(--chart-4))',
|
||||
},
|
||||
other: {
|
||||
label: 'Other',
|
||||
color: 'hsl(var(--chart-5))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card className="flex flex-col">
|
||||
<CardHeader className="items-center pb-0">
|
||||
<CardTitle>Pie Chart - Donut Active</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-1 pb-0">
|
||||
<ChartContainer config={chartConfig} className="mx-auto aspect-square max-h-[250px]">
|
||||
<PieChart>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
|
||||
<Pie
|
||||
data={chartData}
|
||||
dataKey="visitors"
|
||||
nameKey="browser"
|
||||
innerRadius={60}
|
||||
strokeWidth={5}
|
||||
activeIndex={0}
|
||||
activeShape={({ outerRadius = 0, ...props }: PieSectorDataItem) => (
|
||||
<Sector {...props} outerRadius={outerRadius + 10} />
|
||||
)}
|
||||
/>
|
||||
</PieChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { Label, Pie, PieChart } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A donut chart with text'
|
||||
|
||||
const chartData = [
|
||||
{ browser: 'chrome', visitors: 275, fill: 'var(--color-chrome)' },
|
||||
{ browser: 'safari', visitors: 200, fill: 'var(--color-safari)' },
|
||||
{ browser: 'firefox', visitors: 287, fill: 'var(--color-firefox)' },
|
||||
{ browser: 'edge', visitors: 173, fill: 'var(--color-edge)' },
|
||||
{ browser: 'other', visitors: 190, fill: 'var(--color-other)' },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
visitors: {
|
||||
label: 'Visitors',
|
||||
},
|
||||
chrome: {
|
||||
label: 'Chrome',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
safari: {
|
||||
label: 'Safari',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
firefox: {
|
||||
label: 'Firefox',
|
||||
color: 'hsl(var(--chart-3))',
|
||||
},
|
||||
edge: {
|
||||
label: 'Edge',
|
||||
color: 'hsl(var(--chart-4))',
|
||||
},
|
||||
other: {
|
||||
label: 'Other',
|
||||
color: 'hsl(var(--chart-5))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
const totalVisitors = React.useMemo(() => {
|
||||
return chartData.reduce((acc, curr) => acc + curr.visitors, 0)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Card className="flex flex-col">
|
||||
<CardHeader className="items-center pb-0">
|
||||
<CardTitle>Pie Chart - Donut with Text</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-1 pb-0">
|
||||
<ChartContainer config={chartConfig} className="mx-auto aspect-square max-h-[250px]">
|
||||
<PieChart>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
|
||||
<Pie
|
||||
data={chartData}
|
||||
dataKey="visitors"
|
||||
nameKey="browser"
|
||||
innerRadius={60}
|
||||
strokeWidth={5}
|
||||
>
|
||||
<Label
|
||||
content={({ viewBox }) => {
|
||||
if (viewBox && 'cx' in viewBox && 'cy' in viewBox) {
|
||||
return (
|
||||
<text
|
||||
x={viewBox.cx}
|
||||
y={viewBox.cy}
|
||||
textAnchor="middle"
|
||||
dominantBaseline="middle"
|
||||
>
|
||||
<tspan
|
||||
x={viewBox.cx}
|
||||
y={viewBox.cy}
|
||||
className="fill-foreground text-3xl font-bold"
|
||||
>
|
||||
{totalVisitors.toLocaleString()}
|
||||
</tspan>
|
||||
<tspan
|
||||
x={viewBox.cx}
|
||||
y={(viewBox.cy || 0) + 24}
|
||||
className="fill-muted-foreground"
|
||||
>
|
||||
Visitors
|
||||
</tspan>
|
||||
</text>
|
||||
)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Pie>
|
||||
</PieChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { Pie, PieChart } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A donut chart'
|
||||
|
||||
const chartData = [
|
||||
{ browser: 'chrome', visitors: 275, fill: 'var(--color-chrome)' },
|
||||
{ browser: 'safari', visitors: 200, fill: 'var(--color-safari)' },
|
||||
{ browser: 'firefox', visitors: 187, fill: 'var(--color-firefox)' },
|
||||
{ browser: 'edge', visitors: 173, fill: 'var(--color-edge)' },
|
||||
{ browser: 'other', visitors: 90, fill: 'var(--color-other)' },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
visitors: {
|
||||
label: 'Visitors',
|
||||
},
|
||||
chrome: {
|
||||
label: 'Chrome',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
safari: {
|
||||
label: 'Safari',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
firefox: {
|
||||
label: 'Firefox',
|
||||
color: 'hsl(var(--chart-3))',
|
||||
},
|
||||
edge: {
|
||||
label: 'Edge',
|
||||
color: 'hsl(var(--chart-4))',
|
||||
},
|
||||
other: {
|
||||
label: 'Other',
|
||||
color: 'hsl(var(--chart-5))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card className="flex flex-col">
|
||||
<CardHeader className="items-center pb-0">
|
||||
<CardTitle>Pie Chart - Donut</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-1 pb-0">
|
||||
<ChartContainer config={chartConfig} className="mx-auto aspect-square max-h-[250px]">
|
||||
<PieChart>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
|
||||
<Pie data={chartData} dataKey="visitors" nameKey="browser" innerRadius={60} />
|
||||
</PieChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import { Label, Pie, PieChart, Sector } from 'recharts'
|
||||
import { PieSectorDataItem } from 'recharts/types/polar/Pie'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartStyle, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
import {
|
||||
Select_Shadcn_,
|
||||
SelectContent_Shadcn_,
|
||||
SelectItem_Shadcn_,
|
||||
SelectTrigger_Shadcn_,
|
||||
SelectValue_Shadcn_,
|
||||
} from 'ui'
|
||||
|
||||
export const description = 'An interactive pie chart'
|
||||
|
||||
const desktopData = [
|
||||
{ month: 'january', desktop: 186, fill: 'var(--color-january)' },
|
||||
{ month: 'february', desktop: 305, fill: 'var(--color-february)' },
|
||||
{ month: 'march', desktop: 237, fill: 'var(--color-march)' },
|
||||
{ month: 'april', desktop: 173, fill: 'var(--color-april)' },
|
||||
{ month: 'may', desktop: 209, fill: 'var(--color-may)' },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
visitors: {
|
||||
label: 'Visitors',
|
||||
},
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
},
|
||||
mobile: {
|
||||
label: 'Mobile',
|
||||
},
|
||||
january: {
|
||||
label: 'January',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
february: {
|
||||
label: 'February',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
march: {
|
||||
label: 'March',
|
||||
color: 'hsl(var(--chart-3))',
|
||||
},
|
||||
april: {
|
||||
label: 'April',
|
||||
color: 'hsl(var(--chart-4))',
|
||||
},
|
||||
may: {
|
||||
label: 'May',
|
||||
color: 'hsl(var(--chart-5))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
const id = 'pie-interactive'
|
||||
const [activeMonth, setActiveMonth] = React.useState(desktopData[0].month)
|
||||
|
||||
const activeIndex = React.useMemo(
|
||||
() => desktopData.findIndex((item) => item.month === activeMonth),
|
||||
[activeMonth]
|
||||
)
|
||||
const months = React.useMemo(() => desktopData.map((item) => item.month), [])
|
||||
|
||||
return (
|
||||
<Card data-chart={id} className="flex flex-col">
|
||||
<ChartStyle id={id} config={chartConfig} />
|
||||
<CardHeader className="flex-row items-start space-y-0 pb-0">
|
||||
<div className="grid gap-1">
|
||||
<CardTitle>Pie Chart - Interactive</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</div>
|
||||
<Select_Shadcn_ value={activeMonth} onValueChange={setActiveMonth}>
|
||||
<SelectTrigger_Shadcn_
|
||||
className="ml-auto h-7 w-[130px] rounded-lg pl-2.5"
|
||||
aria-label="Select a value"
|
||||
>
|
||||
<SelectValue_Shadcn_ placeholder="Select month" />
|
||||
</SelectTrigger_Shadcn_>
|
||||
<SelectContent_Shadcn_ align="end" className="rounded-xl">
|
||||
{months.map((key) => {
|
||||
const config = chartConfig[key as keyof typeof chartConfig]
|
||||
|
||||
if (!config) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<SelectItem_Shadcn_ key={key} value={key} className="rounded-lg [&_span]:flex">
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
<span
|
||||
className="flex h-3 w-3 shrink-0 rounded-sm"
|
||||
style={{
|
||||
backgroundColor: `var(--color-${key})`,
|
||||
}}
|
||||
/>
|
||||
{config?.label}
|
||||
</div>
|
||||
</SelectItem_Shadcn_>
|
||||
)
|
||||
})}
|
||||
</SelectContent_Shadcn_>
|
||||
</Select_Shadcn_>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-1 justify-center pb-0">
|
||||
<ChartContainer
|
||||
id={id}
|
||||
config={chartConfig}
|
||||
className="mx-auto aspect-square w-full max-w-[300px]"
|
||||
>
|
||||
<PieChart>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
|
||||
<Pie
|
||||
data={desktopData}
|
||||
dataKey="desktop"
|
||||
nameKey="month"
|
||||
innerRadius={60}
|
||||
strokeWidth={5}
|
||||
activeIndex={activeIndex}
|
||||
activeShape={({ outerRadius = 0, ...props }: PieSectorDataItem) => (
|
||||
<g>
|
||||
<Sector {...props} outerRadius={outerRadius + 10} />
|
||||
<Sector
|
||||
{...props}
|
||||
outerRadius={outerRadius + 25}
|
||||
innerRadius={outerRadius + 12}
|
||||
/>
|
||||
</g>
|
||||
)}
|
||||
>
|
||||
<Label
|
||||
content={({ viewBox }) => {
|
||||
if (viewBox && 'cx' in viewBox && 'cy' in viewBox) {
|
||||
return (
|
||||
<text
|
||||
x={viewBox.cx}
|
||||
y={viewBox.cy}
|
||||
textAnchor="middle"
|
||||
dominantBaseline="middle"
|
||||
>
|
||||
<tspan
|
||||
x={viewBox.cx}
|
||||
y={viewBox.cy}
|
||||
className="fill-foreground text-3xl font-bold"
|
||||
>
|
||||
{desktopData[activeIndex].desktop.toLocaleString()}
|
||||
</tspan>
|
||||
<tspan
|
||||
x={viewBox.cx}
|
||||
y={(viewBox.cy || 0) + 24}
|
||||
className="fill-muted-foreground"
|
||||
>
|
||||
Visitors
|
||||
</tspan>
|
||||
</text>
|
||||
)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Pie>
|
||||
</PieChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { Pie, PieChart } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A pie chart with a custom label'
|
||||
|
||||
const chartData = [
|
||||
{ browser: 'chrome', visitors: 275, fill: 'var(--color-chrome)' },
|
||||
{ browser: 'safari', visitors: 200, fill: 'var(--color-safari)' },
|
||||
{ browser: 'firefox', visitors: 187, fill: 'var(--color-firefox)' },
|
||||
{ browser: 'edge', visitors: 173, fill: 'var(--color-edge)' },
|
||||
{ browser: 'other', visitors: 90, fill: 'var(--color-other)' },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
visitors: {
|
||||
label: 'Visitors',
|
||||
},
|
||||
chrome: {
|
||||
label: 'Chrome',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
safari: {
|
||||
label: 'Safari',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
firefox: {
|
||||
label: 'Firefox',
|
||||
color: 'hsl(var(--chart-3))',
|
||||
},
|
||||
edge: {
|
||||
label: 'Edge',
|
||||
color: 'hsl(var(--chart-4))',
|
||||
},
|
||||
other: {
|
||||
label: 'Other',
|
||||
color: 'hsl(var(--chart-5))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card className="flex flex-col">
|
||||
<CardHeader className="items-center pb-0">
|
||||
<CardTitle>Pie Chart - Custom Label</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-1 pb-0">
|
||||
<ChartContainer config={chartConfig} className="mx-auto aspect-square max-h-[250px]">
|
||||
<PieChart>
|
||||
<ChartTooltip content={<ChartTooltipContent nameKey="visitors" hideLabel />} />
|
||||
<Pie
|
||||
data={chartData}
|
||||
dataKey="visitors"
|
||||
labelLine={false}
|
||||
label={({ payload, ...props }) => {
|
||||
return (
|
||||
<text
|
||||
cx={props.cx}
|
||||
cy={props.cy}
|
||||
x={props.x}
|
||||
y={props.y}
|
||||
textAnchor={props.textAnchor}
|
||||
dominantBaseline={props.dominantBaseline}
|
||||
fill="hsla(var(--foreground))"
|
||||
>
|
||||
{`${
|
||||
chartConfig[payload.browser as keyof typeof chartConfig]?.label
|
||||
} (${payload.visitors})`}
|
||||
</text>
|
||||
)
|
||||
}}
|
||||
nameKey="browser"
|
||||
/>
|
||||
</PieChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { LabelList, Pie, PieChart } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A pie chart with a label list'
|
||||
|
||||
const chartData = [
|
||||
{ browser: 'chrome', visitors: 275, fill: 'var(--color-chrome)' },
|
||||
{ browser: 'safari', visitors: 200, fill: 'var(--color-safari)' },
|
||||
{ browser: 'firefox', visitors: 187, fill: 'var(--color-firefox)' },
|
||||
{ browser: 'edge', visitors: 173, fill: 'var(--color-edge)' },
|
||||
{ browser: 'other', visitors: 90, fill: 'var(--color-other)' },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
visitors: {
|
||||
label: 'Visitors',
|
||||
},
|
||||
chrome: {
|
||||
label: 'Chrome',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
safari: {
|
||||
label: 'Safari',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
firefox: {
|
||||
label: 'Firefox',
|
||||
color: 'hsl(var(--chart-3))',
|
||||
},
|
||||
edge: {
|
||||
label: 'Edge',
|
||||
color: 'hsl(var(--chart-4))',
|
||||
},
|
||||
other: {
|
||||
label: 'Other',
|
||||
color: 'hsl(var(--chart-5))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card className="flex flex-col">
|
||||
<CardHeader className="items-center pb-0">
|
||||
<CardTitle>Pie Chart - Label List</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-1 pb-0">
|
||||
<ChartContainer config={chartConfig} className="mx-auto aspect-square max-h-[250px]">
|
||||
<PieChart>
|
||||
<ChartTooltip content={<ChartTooltipContent nameKey="visitors" hideLabel />} />
|
||||
<Pie data={chartData} dataKey="visitors">
|
||||
<LabelList
|
||||
dataKey="browser"
|
||||
className="fill-background"
|
||||
stroke="none"
|
||||
fontSize={12}
|
||||
formatter={(value: keyof typeof chartConfig) => chartConfig[value]?.label}
|
||||
/>
|
||||
</Pie>
|
||||
</PieChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { Pie, PieChart } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A pie chart with a label'
|
||||
|
||||
const chartData = [
|
||||
{ browser: 'chrome', visitors: 275, fill: 'var(--color-chrome)' },
|
||||
{ browser: 'safari', visitors: 200, fill: 'var(--color-safari)' },
|
||||
{ browser: 'firefox', visitors: 187, fill: 'var(--color-firefox)' },
|
||||
{ browser: 'edge', visitors: 173, fill: 'var(--color-edge)' },
|
||||
{ browser: 'other', visitors: 90, fill: 'var(--color-other)' },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
visitors: {
|
||||
label: 'Visitors',
|
||||
},
|
||||
chrome: {
|
||||
label: 'Chrome',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
safari: {
|
||||
label: 'Safari',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
firefox: {
|
||||
label: 'Firefox',
|
||||
color: 'hsl(var(--chart-3))',
|
||||
},
|
||||
edge: {
|
||||
label: 'Edge',
|
||||
color: 'hsl(var(--chart-4))',
|
||||
},
|
||||
other: {
|
||||
label: 'Other',
|
||||
color: 'hsl(var(--chart-5))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card className="flex flex-col">
|
||||
<CardHeader className="items-center pb-0">
|
||||
<CardTitle>Pie Chart - Label</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-1 pb-0">
|
||||
<ChartContainer
|
||||
config={chartConfig}
|
||||
className="mx-auto aspect-square max-h-[250px] pb-0 [&_.recharts-pie-label-text]:fill-foreground"
|
||||
>
|
||||
<PieChart>
|
||||
<ChartTooltip content={<ChartTooltipContent hideLabel />} />
|
||||
<Pie data={chartData} dataKey="visitors" label nameKey="browser" />
|
||||
</PieChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { Pie, PieChart } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartLegend, ChartLegendContent } from 'ui'
|
||||
|
||||
export const description = 'A pie chart with a legend'
|
||||
|
||||
const chartData = [
|
||||
{ browser: 'chrome', visitors: 275, fill: 'var(--color-chrome)' },
|
||||
{ browser: 'safari', visitors: 200, fill: 'var(--color-safari)' },
|
||||
{ browser: 'firefox', visitors: 187, fill: 'var(--color-firefox)' },
|
||||
{ browser: 'edge', visitors: 173, fill: 'var(--color-edge)' },
|
||||
{ browser: 'other', visitors: 90, fill: 'var(--color-other)' },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
visitors: {
|
||||
label: 'Visitors',
|
||||
},
|
||||
chrome: {
|
||||
label: 'Chrome',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
safari: {
|
||||
label: 'Safari',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
firefox: {
|
||||
label: 'Firefox',
|
||||
color: 'hsl(var(--chart-3))',
|
||||
},
|
||||
edge: {
|
||||
label: 'Edge',
|
||||
color: 'hsl(var(--chart-4))',
|
||||
},
|
||||
other: {
|
||||
label: 'Other',
|
||||
color: 'hsl(var(--chart-5))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card className="flex flex-col">
|
||||
<CardHeader className="items-center pb-0">
|
||||
<CardTitle>Pie Chart - Legend</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-1 pb-0">
|
||||
<ChartContainer config={chartConfig} className="mx-auto aspect-square max-h-[300px]">
|
||||
<PieChart>
|
||||
<Pie data={chartData} dataKey="visitors" />
|
||||
<ChartLegend
|
||||
content={<ChartLegendContent nameKey="browser" />}
|
||||
className="-translate-y-2 flex-wrap gap-2 [&>*]:basis-1/4 [&>*]:justify-center"
|
||||
/>
|
||||
</PieChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { Pie, PieChart } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A pie chart with no separator'
|
||||
|
||||
const chartData = [
|
||||
{ browser: 'chrome', visitors: 275, fill: 'var(--color-chrome)' },
|
||||
{ browser: 'safari', visitors: 200, fill: 'var(--color-safari)' },
|
||||
{ browser: 'firefox', visitors: 187, fill: 'var(--color-firefox)' },
|
||||
{ browser: 'edge', visitors: 173, fill: 'var(--color-edge)' },
|
||||
{ browser: 'other', visitors: 90, fill: 'var(--color-other)' },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
visitors: {
|
||||
label: 'Visitors',
|
||||
},
|
||||
chrome: {
|
||||
label: 'Chrome',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
safari: {
|
||||
label: 'Safari',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
firefox: {
|
||||
label: 'Firefox',
|
||||
color: 'hsl(var(--chart-3))',
|
||||
},
|
||||
edge: {
|
||||
label: 'Edge',
|
||||
color: 'hsl(var(--chart-4))',
|
||||
},
|
||||
other: {
|
||||
label: 'Other',
|
||||
color: 'hsl(var(--chart-5))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card className="flex flex-col">
|
||||
<CardHeader className="items-center pb-0">
|
||||
<CardTitle>Pie Chart - Separator None</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-1 pb-0">
|
||||
<ChartContainer config={chartConfig} className="mx-auto aspect-square max-h-[250px]">
|
||||
<PieChart>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
|
||||
<Pie data={chartData} dataKey="visitors" nameKey="browser" stroke="0" />
|
||||
</PieChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { Pie, PieChart } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A simple pie chart'
|
||||
|
||||
const chartData = [
|
||||
{ browser: 'chrome', visitors: 275, fill: 'var(--color-chrome)' },
|
||||
{ browser: 'safari', visitors: 200, fill: 'var(--color-safari)' },
|
||||
{ browser: 'firefox', visitors: 187, fill: 'var(--color-firefox)' },
|
||||
{ browser: 'edge', visitors: 173, fill: 'var(--color-edge)' },
|
||||
{ browser: 'other', visitors: 90, fill: 'var(--color-other)' },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
visitors: {
|
||||
label: 'Visitors',
|
||||
},
|
||||
chrome: {
|
||||
label: 'Chrome',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
safari: {
|
||||
label: 'Safari',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
firefox: {
|
||||
label: 'Firefox',
|
||||
color: 'hsl(var(--chart-3))',
|
||||
},
|
||||
edge: {
|
||||
label: 'Edge',
|
||||
color: 'hsl(var(--chart-4))',
|
||||
},
|
||||
other: {
|
||||
label: 'Other',
|
||||
color: 'hsl(var(--chart-5))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card className="flex flex-col">
|
||||
<CardHeader className="items-center pb-0">
|
||||
<CardTitle>Pie Chart</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-1 pb-0">
|
||||
<ChartContainer config={chartConfig} className="mx-auto aspect-square max-h-[250px]">
|
||||
<PieChart>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
|
||||
<Pie data={chartData} dataKey="visitors" nameKey="browser" />
|
||||
</PieChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { Label, Pie, PieChart } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A pie chart with stacked sections'
|
||||
|
||||
const desktopData = [
|
||||
{ month: 'january', desktop: 186, fill: 'var(--color-january)' },
|
||||
{ month: 'february', desktop: 305, fill: 'var(--color-february)' },
|
||||
{ month: 'march', desktop: 237, fill: 'var(--color-march)' },
|
||||
{ month: 'april', desktop: 173, fill: 'var(--color-april)' },
|
||||
{ month: 'may', desktop: 209, fill: 'var(--color-may)' },
|
||||
]
|
||||
|
||||
const mobileData = [
|
||||
{ month: 'january', mobile: 80, fill: 'var(--color-january)' },
|
||||
{ month: 'february', mobile: 200, fill: 'var(--color-february)' },
|
||||
{ month: 'march', mobile: 120, fill: 'var(--color-march)' },
|
||||
{ month: 'april', mobile: 190, fill: 'var(--color-april)' },
|
||||
{ month: 'may', mobile: 130, fill: 'var(--color-may)' },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
visitors: {
|
||||
label: 'Visitors',
|
||||
},
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
},
|
||||
mobile: {
|
||||
label: 'Mobile',
|
||||
},
|
||||
january: {
|
||||
label: 'January',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
february: {
|
||||
label: 'February',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
march: {
|
||||
label: 'March',
|
||||
color: 'hsl(var(--chart-3))',
|
||||
},
|
||||
april: {
|
||||
label: 'April',
|
||||
color: 'hsl(var(--chart-4))',
|
||||
},
|
||||
may: {
|
||||
label: 'May',
|
||||
color: 'hsl(var(--chart-5))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card className="flex flex-col">
|
||||
<CardHeader className="items-center pb-0">
|
||||
<CardTitle>Pie Chart - Stacked</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-1 pb-0">
|
||||
<ChartContainer config={chartConfig} className="mx-auto aspect-square max-h-[250px]">
|
||||
<PieChart>
|
||||
<ChartTooltip
|
||||
content={
|
||||
<ChartTooltipContent
|
||||
labelKey="visitors"
|
||||
nameKey="month"
|
||||
indicator="line"
|
||||
labelFormatter={(_, payload) => {
|
||||
return chartConfig[payload?.[0].dataKey as keyof typeof chartConfig].label
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Pie data={desktopData} dataKey="desktop" outerRadius={60} />
|
||||
<Pie data={mobileData} dataKey="mobile" innerRadius={70} outerRadius={90} />
|
||||
</PieChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { PolarAngleAxis, PolarGrid, Radar, RadarChart } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A radar chart'
|
||||
|
||||
const chartData = [
|
||||
{ month: 'January', desktop: 186 },
|
||||
{ month: 'February', desktop: 305 },
|
||||
{ month: 'March', desktop: 237 },
|
||||
{ month: 'April', desktop: 273 },
|
||||
{ month: 'May', desktop: 209 },
|
||||
{ month: 'June', desktop: 214 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="items-center pb-4">
|
||||
<CardTitle>Radar Chart</CardTitle>
|
||||
<CardDescription>Showing total visitors for the last 6 months</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="pb-0">
|
||||
<ChartContainer config={chartConfig} className="mx-auto aspect-square max-h-[250px]">
|
||||
<RadarChart data={chartData}>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent />} />
|
||||
<PolarAngleAxis dataKey="month" />
|
||||
<PolarGrid />
|
||||
<Radar dataKey="desktop" fill="var(--color-desktop)" fillOpacity={0.6} />
|
||||
</RadarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2 leading-none text-muted-foreground">
|
||||
January - June 2024
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { PolarAngleAxis, PolarGrid, Radar, RadarChart } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A radar chart with dots'
|
||||
|
||||
const chartData = [
|
||||
{ month: 'January', desktop: 186 },
|
||||
{ month: 'February', desktop: 305 },
|
||||
{ month: 'March', desktop: 237 },
|
||||
{ month: 'April', desktop: 273 },
|
||||
{ month: 'May', desktop: 209 },
|
||||
{ month: 'June', desktop: 214 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="items-center">
|
||||
<CardTitle>Radar Chart - Dots</CardTitle>
|
||||
<CardDescription>Showing total visitors for the last 6 months</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="pb-0">
|
||||
<ChartContainer config={chartConfig} className="mx-auto aspect-square max-h-[250px]">
|
||||
<RadarChart data={chartData}>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent />} />
|
||||
<PolarAngleAxis dataKey="month" />
|
||||
<PolarGrid />
|
||||
<Radar
|
||||
dataKey="desktop"
|
||||
fill="var(--color-desktop)"
|
||||
fillOpacity={0.6}
|
||||
dot={{
|
||||
r: 4,
|
||||
fillOpacity: 1,
|
||||
}}
|
||||
/>
|
||||
</RadarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2 leading-none text-muted-foreground">
|
||||
January - June 2024
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { PolarAngleAxis, PolarGrid, Radar, RadarChart } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A radar chart with a grid and circle fill'
|
||||
|
||||
const chartData = [
|
||||
{ month: 'January', desktop: 186 },
|
||||
{ month: 'February', desktop: 285 },
|
||||
{ month: 'March', desktop: 237 },
|
||||
{ month: 'April', desktop: 203 },
|
||||
{ month: 'May', desktop: 209 },
|
||||
{ month: 'June', desktop: 264 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="items-center pb-4">
|
||||
<CardTitle>Radar Chart - Grid Circle Filled</CardTitle>
|
||||
<CardDescription>Showing total visitors for the last 6 months</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="pb-0">
|
||||
<ChartContainer config={chartConfig} className="mx-auto aspect-square max-h-[250px]">
|
||||
<RadarChart data={chartData}>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent />} />
|
||||
<PolarGrid className="fill-[--color-desktop] opacity-20" gridType="circle" />
|
||||
<PolarAngleAxis dataKey="month" />
|
||||
<Radar dataKey="desktop" fill="var(--color-desktop)" fillOpacity={0.5} />
|
||||
</RadarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2 leading-none text-muted-foreground">
|
||||
January - June 2024
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { PolarAngleAxis, PolarGrid, Radar, RadarChart } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A radar chart with a grid and circle fill'
|
||||
|
||||
const chartData = [
|
||||
{ month: 'January', desktop: 186 },
|
||||
{ month: 'February', desktop: 305 },
|
||||
{ month: 'March', desktop: 237 },
|
||||
{ month: 'April', desktop: 203 },
|
||||
{ month: 'May', desktop: 209 },
|
||||
{ month: 'June', desktop: 214 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="items-center pb-4">
|
||||
<CardTitle>Radar Chart - Grid Circle - No lines</CardTitle>
|
||||
<CardDescription>Showing total visitors for the last 6 months</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="pb-0">
|
||||
<ChartContainer config={chartConfig} className="mx-auto aspect-square max-h-[250px]">
|
||||
<RadarChart data={chartData}>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
|
||||
<PolarGrid gridType="circle" radialLines={false} />
|
||||
<PolarAngleAxis dataKey="month" />
|
||||
<Radar
|
||||
dataKey="desktop"
|
||||
fill="var(--color-desktop)"
|
||||
fillOpacity={0.6}
|
||||
dot={{
|
||||
r: 4,
|
||||
fillOpacity: 1,
|
||||
}}
|
||||
/>
|
||||
</RadarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2 leading-none text-muted-foreground">
|
||||
January - June 2024
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { PolarAngleAxis, PolarGrid, Radar, RadarChart } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A radar chart with a grid and circle'
|
||||
|
||||
const chartData = [
|
||||
{ month: 'January', desktop: 186 },
|
||||
{ month: 'February', desktop: 305 },
|
||||
{ month: 'March', desktop: 237 },
|
||||
{ month: 'April', desktop: 273 },
|
||||
{ month: 'May', desktop: 209 },
|
||||
{ month: 'June', desktop: 214 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="items-center pb-4">
|
||||
<CardTitle>Radar Chart - Grid Circle</CardTitle>
|
||||
<CardDescription>Showing total visitors for the last 6 months</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="pb-0">
|
||||
<ChartContainer config={chartConfig} className="mx-auto aspect-square max-h-[250px]">
|
||||
<RadarChart data={chartData}>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
|
||||
<PolarGrid gridType="circle" />
|
||||
<PolarAngleAxis dataKey="month" />
|
||||
<Radar
|
||||
dataKey="desktop"
|
||||
fill="var(--color-desktop)"
|
||||
fillOpacity={0.6}
|
||||
dot={{
|
||||
r: 4,
|
||||
fillOpacity: 1,
|
||||
}}
|
||||
/>
|
||||
</RadarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2 leading-none text-muted-foreground">
|
||||
January - June 2024
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { PolarAngleAxis, PolarGrid, Radar, RadarChart } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A radar chart with a custom grid'
|
||||
|
||||
const chartData = [
|
||||
{ month: 'January', desktop: 186 },
|
||||
{ month: 'February', desktop: 305 },
|
||||
{ month: 'March', desktop: 237 },
|
||||
{ month: 'April', desktop: 273 },
|
||||
{ month: 'May', desktop: 209 },
|
||||
{ month: 'June', desktop: 214 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="items-center pb-4">
|
||||
<CardTitle>Radar Chart - Grid Custom</CardTitle>
|
||||
<CardDescription>Showing total visitors for the last 6 months</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="pb-0">
|
||||
<ChartContainer config={chartConfig} className="mx-auto aspect-square max-h-[250px]">
|
||||
<RadarChart data={chartData}>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
|
||||
<PolarGrid radialLines={false} polarRadius={[90]} strokeWidth={1} />
|
||||
<PolarAngleAxis dataKey="month" />
|
||||
<Radar dataKey="desktop" fill="var(--color-desktop)" fillOpacity={0.6} />
|
||||
</RadarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2 leading-none text-muted-foreground">
|
||||
January - June 2024
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { PolarAngleAxis, PolarGrid, Radar, RadarChart } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A radar chart with a grid filled'
|
||||
|
||||
const chartData = [
|
||||
{ month: 'January', desktop: 186 },
|
||||
{ month: 'February', desktop: 285 },
|
||||
{ month: 'March', desktop: 237 },
|
||||
{ month: 'April', desktop: 203 },
|
||||
{ month: 'May', desktop: 209 },
|
||||
{ month: 'June', desktop: 264 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="items-center pb-4">
|
||||
<CardTitle>Radar Chart - Grid Filled</CardTitle>
|
||||
<CardDescription>Showing total visitors for the last 6 months</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="pb-0">
|
||||
<ChartContainer config={chartConfig} className="mx-auto aspect-square max-h-[250px]">
|
||||
<RadarChart data={chartData}>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
|
||||
<PolarGrid className="fill-[--color-desktop] opacity-20" />
|
||||
<PolarAngleAxis dataKey="month" />
|
||||
<Radar dataKey="desktop" fill="var(--color-desktop)" fillOpacity={0.5} />
|
||||
</RadarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2 leading-none text-muted-foreground">
|
||||
January - June 2024
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { PolarAngleAxis, Radar, RadarChart } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A radar chart with no grid'
|
||||
|
||||
const chartData = [
|
||||
{ month: 'January', desktop: 186 },
|
||||
{ month: 'February', desktop: 305 },
|
||||
{ month: 'March', desktop: 237 },
|
||||
{ month: 'April', desktop: 273 },
|
||||
{ month: 'May', desktop: 209 },
|
||||
{ month: 'June', desktop: 214 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="items-center pb-4">
|
||||
<CardTitle>Radar Chart - Grid None</CardTitle>
|
||||
<CardDescription>Showing total visitors for the last 6 months</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="pb-0">
|
||||
<ChartContainer config={chartConfig} className="mx-auto aspect-square max-h-[250px]">
|
||||
<RadarChart data={chartData}>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
|
||||
<PolarAngleAxis dataKey="month" />
|
||||
<Radar
|
||||
dataKey="desktop"
|
||||
fill="var(--color-desktop)"
|
||||
fillOpacity={0.6}
|
||||
dot={{
|
||||
r: 4,
|
||||
fillOpacity: 1,
|
||||
}}
|
||||
/>
|
||||
</RadarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2 leading-none text-muted-foreground">
|
||||
January - June 2024
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { ArrowDownFromLine, ArrowUpFromLine, TrendingUp } from 'lucide-react'
|
||||
import { PolarAngleAxis, PolarGrid, Radar, RadarChart } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import {
|
||||
ChartConfig,
|
||||
ChartContainer,
|
||||
ChartLegend,
|
||||
ChartLegendContent,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
} from 'ui'
|
||||
|
||||
export const description = 'A radar chart with icons'
|
||||
|
||||
const chartData = [
|
||||
{ month: 'January', desktop: 186, mobile: 80 },
|
||||
{ month: 'February', desktop: 305, mobile: 200 },
|
||||
{ month: 'March', desktop: 237, mobile: 120 },
|
||||
{ month: 'April', desktop: 73, mobile: 190 },
|
||||
{ month: 'May', desktop: 209, mobile: 130 },
|
||||
{ month: 'June', desktop: 214, mobile: 140 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
icon: ArrowDownFromLine,
|
||||
},
|
||||
mobile: {
|
||||
label: 'Mobile',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
icon: ArrowUpFromLine,
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="items-center pb-4">
|
||||
<CardTitle>Radar Chart - Icons</CardTitle>
|
||||
<CardDescription>Showing total visitors for the last 6 months</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig} className="mx-auto aspect-square max-h-[250px]">
|
||||
<RadarChart
|
||||
data={chartData}
|
||||
margin={{
|
||||
top: -40,
|
||||
bottom: -10,
|
||||
}}
|
||||
>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent indicator="line" />} />
|
||||
<PolarAngleAxis dataKey="month" />
|
||||
<PolarGrid />
|
||||
<Radar dataKey="desktop" fill="var(--color-desktop)" fillOpacity={0.6} />
|
||||
<Radar dataKey="mobile" fill="var(--color-mobile)" />
|
||||
<ChartLegend className="mt-8" content={<ChartLegendContent />} />
|
||||
</RadarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 pt-4 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2 leading-none text-muted-foreground">
|
||||
January - June 2024
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { PolarAngleAxis, PolarGrid, Radar, RadarChart } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A radar chart with a custom label'
|
||||
|
||||
const chartData = [
|
||||
{ month: 'January', desktop: 186, mobile: 80 },
|
||||
{ month: 'February', desktop: 305, mobile: 200 },
|
||||
{ month: 'March', desktop: 237, mobile: 120 },
|
||||
{ month: 'April', desktop: 73, mobile: 190 },
|
||||
{ month: 'May', desktop: 209, mobile: 130 },
|
||||
{ month: 'June', desktop: 214, mobile: 140 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
mobile: {
|
||||
label: 'Mobile',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="items-center pb-4">
|
||||
<CardTitle>Radar Chart - Custom Label</CardTitle>
|
||||
<CardDescription>Showing total visitors for the last 6 months</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="pb-0">
|
||||
<ChartContainer config={chartConfig} className="mx-auto aspect-square max-h-[250px]">
|
||||
<RadarChart
|
||||
data={chartData}
|
||||
margin={{
|
||||
top: 10,
|
||||
right: 10,
|
||||
bottom: 10,
|
||||
left: 10,
|
||||
}}
|
||||
>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent indicator="line" />} />
|
||||
<PolarAngleAxis
|
||||
dataKey="month"
|
||||
tick={({ x, y, textAnchor, value, index, ...props }) => {
|
||||
const data = chartData[index]
|
||||
|
||||
return (
|
||||
<text
|
||||
x={x}
|
||||
y={index === 0 ? y - 10 : y}
|
||||
textAnchor={textAnchor}
|
||||
fontSize={13}
|
||||
fontWeight={500}
|
||||
{...props}
|
||||
>
|
||||
<tspan>{data.desktop}</tspan>
|
||||
<tspan className="fill-muted-foreground">/</tspan>
|
||||
<tspan>{data.mobile}</tspan>
|
||||
<tspan x={x} dy={'1rem'} fontSize={12} className="fill-muted-foreground">
|
||||
{data.month}
|
||||
</tspan>
|
||||
</text>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
|
||||
<PolarGrid />
|
||||
<Radar dataKey="desktop" fill="var(--color-desktop)" fillOpacity={0.6} />
|
||||
<Radar dataKey="mobile" fill="var(--color-mobile)" />
|
||||
</RadarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2 leading-none text-muted-foreground">
|
||||
January - June 2024
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { PolarAngleAxis, PolarGrid, Radar, RadarChart } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import {
|
||||
ChartConfig,
|
||||
ChartContainer,
|
||||
ChartLegend,
|
||||
ChartLegendContent,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
} from 'ui'
|
||||
|
||||
export const description = 'A radar chart with a legend'
|
||||
|
||||
const chartData = [
|
||||
{ month: 'January', desktop: 186, mobile: 80 },
|
||||
{ month: 'February', desktop: 305, mobile: 200 },
|
||||
{ month: 'March', desktop: 237, mobile: 120 },
|
||||
{ month: 'April', desktop: 73, mobile: 190 },
|
||||
{ month: 'May', desktop: 209, mobile: 130 },
|
||||
{ month: 'June', desktop: 214, mobile: 140 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
mobile: {
|
||||
label: 'Mobile',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="items-center pb-4">
|
||||
<CardTitle>Radar Chart - Legend</CardTitle>
|
||||
<CardDescription>Showing total visitors for the last 6 months</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig} className="mx-auto aspect-square max-h-[250px]">
|
||||
<RadarChart
|
||||
data={chartData}
|
||||
margin={{
|
||||
top: -40,
|
||||
bottom: -10,
|
||||
}}
|
||||
>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent indicator="line" />} />
|
||||
<PolarAngleAxis dataKey="month" />
|
||||
<PolarGrid />
|
||||
<Radar dataKey="desktop" fill="var(--color-desktop)" fillOpacity={0.6} />
|
||||
<Radar dataKey="mobile" fill="var(--color-mobile)" />
|
||||
<ChartLegend className="mt-8" content={<ChartLegendContent />} />
|
||||
</RadarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 pt-4 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2 leading-none text-muted-foreground">
|
||||
January - June 2024
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { PolarAngleAxis, PolarGrid, Radar, RadarChart } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A radar chart with lines only'
|
||||
|
||||
const chartData = [
|
||||
{ month: 'January', desktop: 186, mobile: 160 },
|
||||
{ month: 'February', desktop: 185, mobile: 170 },
|
||||
{ month: 'March', desktop: 207, mobile: 180 },
|
||||
{ month: 'April', desktop: 173, mobile: 160 },
|
||||
{ month: 'May', desktop: 160, mobile: 190 },
|
||||
{ month: 'June', desktop: 174, mobile: 204 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
mobile: {
|
||||
label: 'Mobile',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="items-center pb-4">
|
||||
<CardTitle>Radar Chart - Lines Only</CardTitle>
|
||||
<CardDescription>Showing total visitors for the last 6 months</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="pb-0">
|
||||
<ChartContainer config={chartConfig} className="mx-auto aspect-square max-h-[250px]">
|
||||
<RadarChart data={chartData}>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent indicator="line" />} />
|
||||
<PolarAngleAxis dataKey="month" />
|
||||
<PolarGrid radialLines={false} />
|
||||
<Radar
|
||||
dataKey="desktop"
|
||||
fill="var(--color-desktop)"
|
||||
fillOpacity={0}
|
||||
stroke="var(--color-desktop)"
|
||||
strokeWidth={2}
|
||||
/>
|
||||
<Radar
|
||||
dataKey="mobile"
|
||||
fill="var(--color-mobile)"
|
||||
fillOpacity={0}
|
||||
stroke="var(--color-mobile)"
|
||||
strokeWidth={2}
|
||||
/>
|
||||
</RadarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2 leading-none text-muted-foreground">
|
||||
January - June 2024
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { PolarAngleAxis, PolarGrid, Radar, RadarChart } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A radar chart with multiple data'
|
||||
|
||||
const chartData = [
|
||||
{ month: 'January', desktop: 186, mobile: 80 },
|
||||
{ month: 'February', desktop: 305, mobile: 200 },
|
||||
{ month: 'March', desktop: 237, mobile: 120 },
|
||||
{ month: 'April', desktop: 73, mobile: 190 },
|
||||
{ month: 'May', desktop: 209, mobile: 130 },
|
||||
{ month: 'June', desktop: 214, mobile: 140 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
mobile: {
|
||||
label: 'Mobile',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="items-center pb-4">
|
||||
<CardTitle>Radar Chart - Multiple</CardTitle>
|
||||
<CardDescription>Showing total visitors for the last 6 months</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="pb-0">
|
||||
<ChartContainer config={chartConfig} className="mx-auto aspect-square max-h-[250px]">
|
||||
<RadarChart data={chartData}>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent indicator="line" />} />
|
||||
<PolarAngleAxis dataKey="month" />
|
||||
<PolarGrid />
|
||||
<Radar dataKey="desktop" fill="var(--color-desktop)" fillOpacity={0.6} />
|
||||
<Radar dataKey="mobile" fill="var(--color-mobile)" />
|
||||
</RadarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2 leading-none text-muted-foreground">
|
||||
January - June 2024
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { PolarAngleAxis, PolarGrid, PolarRadiusAxis, Radar, RadarChart } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A radar chart with a radius axis'
|
||||
|
||||
const chartData = [
|
||||
{ month: 'January', desktop: 186, mobile: 80 },
|
||||
{ month: 'February', desktop: 305, mobile: 200 },
|
||||
{ month: 'March', desktop: 237, mobile: 120 },
|
||||
{ month: 'April', desktop: 73, mobile: 190 },
|
||||
{ month: 'May', desktop: 209, mobile: 130 },
|
||||
{ month: 'June', desktop: 214, mobile: 140 },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
mobile: {
|
||||
label: 'Mobile',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="items-center pb-4">
|
||||
<CardTitle>Radar Chart - Radius Axis</CardTitle>
|
||||
<CardDescription>Showing total visitors for the last 6 months</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="pb-0">
|
||||
<ChartContainer config={chartConfig} className="mx-auto aspect-square max-h-[250px]">
|
||||
<RadarChart data={chartData}>
|
||||
<ChartTooltip
|
||||
cursor={false}
|
||||
content={<ChartTooltipContent indicator="line" labelKey="month" />}
|
||||
/>
|
||||
<PolarGrid />
|
||||
<Radar dataKey="desktop" fill="var(--color-desktop)" fillOpacity={0.6} />
|
||||
<Radar dataKey="mobile" fill="var(--color-mobile)" />
|
||||
<PolarRadiusAxis
|
||||
angle={60}
|
||||
stroke="hsla(var(--foreground))"
|
||||
orientation="middle"
|
||||
axisLine={false}
|
||||
/>
|
||||
</RadarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2 leading-none text-muted-foreground">
|
||||
January - June 2024
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { PolarGrid, RadialBar, RadialBarChart } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A radial chart with a grid'
|
||||
|
||||
const chartData = [
|
||||
{ browser: 'chrome', visitors: 275, fill: 'var(--color-chrome)' },
|
||||
{ browser: 'safari', visitors: 200, fill: 'var(--color-safari)' },
|
||||
{ browser: 'firefox', visitors: 187, fill: 'var(--color-firefox)' },
|
||||
{ browser: 'edge', visitors: 173, fill: 'var(--color-edge)' },
|
||||
{ browser: 'other', visitors: 90, fill: 'var(--color-other)' },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
visitors: {
|
||||
label: 'Visitors',
|
||||
},
|
||||
chrome: {
|
||||
label: 'Chrome',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
safari: {
|
||||
label: 'Safari',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
firefox: {
|
||||
label: 'Firefox',
|
||||
color: 'hsl(var(--chart-3))',
|
||||
},
|
||||
edge: {
|
||||
label: 'Edge',
|
||||
color: 'hsl(var(--chart-4))',
|
||||
},
|
||||
other: {
|
||||
label: 'Other',
|
||||
color: 'hsl(var(--chart-5))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card className="flex flex-col">
|
||||
<CardHeader className="items-center pb-0">
|
||||
<CardTitle>Radial Chart - Grid</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-1 pb-0">
|
||||
<ChartContainer config={chartConfig} className="mx-auto aspect-square max-h-[250px]">
|
||||
<RadialBarChart data={chartData} innerRadius={30} outerRadius={100}>
|
||||
<ChartTooltip
|
||||
cursor={false}
|
||||
content={<ChartTooltipContent hideLabel nameKey="browser" />}
|
||||
/>
|
||||
<PolarGrid gridType="circle" />
|
||||
<RadialBar dataKey="visitors" />
|
||||
</RadialBarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { LabelList, RadialBar, RadialBarChart } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A radial chart with a label'
|
||||
|
||||
const chartData = [
|
||||
{ browser: 'chrome', visitors: 275, fill: 'var(--color-chrome)' },
|
||||
{ browser: 'safari', visitors: 200, fill: 'var(--color-safari)' },
|
||||
{ browser: 'firefox', visitors: 187, fill: 'var(--color-firefox)' },
|
||||
{ browser: 'edge', visitors: 173, fill: 'var(--color-edge)' },
|
||||
{ browser: 'other', visitors: 90, fill: 'var(--color-other)' },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
visitors: {
|
||||
label: 'Visitors',
|
||||
},
|
||||
chrome: {
|
||||
label: 'Chrome',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
safari: {
|
||||
label: 'Safari',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
firefox: {
|
||||
label: 'Firefox',
|
||||
color: 'hsl(var(--chart-3))',
|
||||
},
|
||||
edge: {
|
||||
label: 'Edge',
|
||||
color: 'hsl(var(--chart-4))',
|
||||
},
|
||||
other: {
|
||||
label: 'Other',
|
||||
color: 'hsl(var(--chart-5))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card className="flex flex-col">
|
||||
<CardHeader className="items-center pb-0">
|
||||
<CardTitle>Radial Chart - Label</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-1 pb-0">
|
||||
<ChartContainer config={chartConfig} className="mx-auto aspect-square max-h-[250px]">
|
||||
<RadialBarChart
|
||||
data={chartData}
|
||||
startAngle={-90}
|
||||
endAngle={380}
|
||||
innerRadius={30}
|
||||
outerRadius={110}
|
||||
>
|
||||
<ChartTooltip
|
||||
cursor={false}
|
||||
content={<ChartTooltipContent hideLabel nameKey="browser" />}
|
||||
/>
|
||||
<RadialBar dataKey="visitors" background>
|
||||
<LabelList
|
||||
position="insideStart"
|
||||
dataKey="browser"
|
||||
className="fill-white capitalize mix-blend-luminosity"
|
||||
fontSize={11}
|
||||
/>
|
||||
</RadialBar>
|
||||
</RadialBarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { Label, PolarGrid, PolarRadiusAxis, RadialBar, RadialBarChart } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer } from 'ui'
|
||||
|
||||
export const description = 'A radial chart with a custom shape'
|
||||
|
||||
const chartData = [{ browser: 'safari', visitors: 1260, fill: 'var(--color-safari)' }]
|
||||
|
||||
const chartConfig = {
|
||||
visitors: {
|
||||
label: 'Visitors',
|
||||
},
|
||||
safari: {
|
||||
label: 'Safari',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card className="flex flex-col">
|
||||
<CardHeader className="items-center pb-0">
|
||||
<CardTitle>Radial Chart - Shape</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-1 pb-0">
|
||||
<ChartContainer config={chartConfig} className="mx-auto aspect-square max-h-[250px]">
|
||||
<RadialBarChart data={chartData} endAngle={100} innerRadius={80} outerRadius={140}>
|
||||
<PolarGrid
|
||||
gridType="circle"
|
||||
radialLines={false}
|
||||
stroke="none"
|
||||
className="first:fill-muted last:fill-background"
|
||||
polarRadius={[86, 74]}
|
||||
/>
|
||||
<RadialBar dataKey="visitors" background />
|
||||
<PolarRadiusAxis tick={false} tickLine={false} axisLine={false}>
|
||||
<Label
|
||||
content={({ viewBox }) => {
|
||||
if (viewBox && 'cx' in viewBox && 'cy' in viewBox) {
|
||||
return (
|
||||
<text
|
||||
x={viewBox.cx}
|
||||
y={viewBox.cy}
|
||||
textAnchor="middle"
|
||||
dominantBaseline="middle"
|
||||
>
|
||||
<tspan
|
||||
x={viewBox.cx}
|
||||
y={viewBox.cy}
|
||||
className="fill-foreground text-4xl font-bold"
|
||||
>
|
||||
{chartData[0].visitors.toLocaleString()}
|
||||
</tspan>
|
||||
<tspan
|
||||
x={viewBox.cx}
|
||||
y={(viewBox.cy || 0) + 24}
|
||||
className="fill-muted-foreground"
|
||||
>
|
||||
Visitors
|
||||
</tspan>
|
||||
</text>
|
||||
)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</PolarRadiusAxis>
|
||||
</RadialBarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { RadialBar, RadialBarChart } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A radial chart'
|
||||
|
||||
const chartData = [
|
||||
{ browser: 'chrome', visitors: 275, fill: 'var(--color-chrome)' },
|
||||
{ browser: 'safari', visitors: 200, fill: 'var(--color-safari)' },
|
||||
{ browser: 'firefox', visitors: 187, fill: 'var(--color-firefox)' },
|
||||
{ browser: 'edge', visitors: 173, fill: 'var(--color-edge)' },
|
||||
{ browser: 'other', visitors: 90, fill: 'var(--color-other)' },
|
||||
]
|
||||
|
||||
const chartConfig = {
|
||||
visitors: {
|
||||
label: 'Visitors',
|
||||
},
|
||||
chrome: {
|
||||
label: 'Chrome',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
safari: {
|
||||
label: 'Safari',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
firefox: {
|
||||
label: 'Firefox',
|
||||
color: 'hsl(var(--chart-3))',
|
||||
},
|
||||
edge: {
|
||||
label: 'Edge',
|
||||
color: 'hsl(var(--chart-4))',
|
||||
},
|
||||
other: {
|
||||
label: 'Other',
|
||||
color: 'hsl(var(--chart-5))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card className="flex flex-col">
|
||||
<CardHeader className="items-center pb-0">
|
||||
<CardTitle>Radial Chart</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-1 pb-0">
|
||||
<ChartContainer config={chartConfig} className="mx-auto aspect-square max-h-[250px]">
|
||||
<RadialBarChart data={chartData} innerRadius={30} outerRadius={110}>
|
||||
<ChartTooltip
|
||||
cursor={false}
|
||||
content={<ChartTooltipContent hideLabel nameKey="browser" />}
|
||||
/>
|
||||
<RadialBar dataKey="visitors" background />
|
||||
</RadialBarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { Label, PolarRadiusAxis, RadialBar, RadialBarChart } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from 'ui'
|
||||
|
||||
export const description = 'A radial chart with stacked sections'
|
||||
|
||||
const chartData = [{ month: 'january', desktop: 1260, mobile: 570 }]
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'hsl(var(--chart-1))',
|
||||
},
|
||||
mobile: {
|
||||
label: 'Mobile',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
const totalVisitors = chartData[0].desktop + chartData[0].mobile
|
||||
|
||||
return (
|
||||
<Card className="flex flex-col">
|
||||
<CardHeader className="items-center pb-0">
|
||||
<CardTitle>Radial Chart - Stacked</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-1 items-center pb-0">
|
||||
<ChartContainer config={chartConfig} className="mx-auto aspect-square w-full max-w-[250px]">
|
||||
<RadialBarChart data={chartData} endAngle={180} innerRadius={80} outerRadius={130}>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent hideLabel />} />
|
||||
<PolarRadiusAxis tick={false} tickLine={false} axisLine={false}>
|
||||
<Label
|
||||
content={({ viewBox }) => {
|
||||
if (viewBox && 'cx' in viewBox && 'cy' in viewBox) {
|
||||
return (
|
||||
<text x={viewBox.cx} y={viewBox.cy} textAnchor="middle">
|
||||
<tspan
|
||||
x={viewBox.cx}
|
||||
y={(viewBox.cy || 0) - 16}
|
||||
className="fill-foreground text-2xl font-bold"
|
||||
>
|
||||
{totalVisitors.toLocaleString()}
|
||||
</tspan>
|
||||
<tspan
|
||||
x={viewBox.cx}
|
||||
y={(viewBox.cy || 0) + 4}
|
||||
className="fill-muted-foreground"
|
||||
>
|
||||
Visitors
|
||||
</tspan>
|
||||
</text>
|
||||
)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</PolarRadiusAxis>
|
||||
<RadialBar
|
||||
dataKey="desktop"
|
||||
stackId="a"
|
||||
cornerRadius={5}
|
||||
fill="var(--color-desktop)"
|
||||
className="stroke-transparent stroke-2"
|
||||
/>
|
||||
<RadialBar
|
||||
dataKey="mobile"
|
||||
fill="var(--color-mobile)"
|
||||
stackId="a"
|
||||
cornerRadius={5}
|
||||
className="stroke-transparent stroke-2"
|
||||
/>
|
||||
</RadialBarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { Label, PolarGrid, PolarRadiusAxis, RadialBar, RadialBarChart } from 'recharts'
|
||||
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from 'ui'
|
||||
import { ChartConfig, ChartContainer } from 'ui'
|
||||
|
||||
export const description = 'A radial chart with text'
|
||||
|
||||
const chartData = [{ browser: 'safari', visitors: 200, fill: 'var(--color-safari)' }]
|
||||
|
||||
const chartConfig = {
|
||||
visitors: {
|
||||
label: 'Visitors',
|
||||
},
|
||||
safari: {
|
||||
label: 'Safari',
|
||||
color: 'hsl(var(--chart-2))',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export default function Component() {
|
||||
return (
|
||||
<Card className="flex flex-col">
|
||||
<CardHeader className="items-center pb-0">
|
||||
<CardTitle>Radial Chart - Text</CardTitle>
|
||||
<CardDescription>January - June 2024</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-1 pb-0">
|
||||
<ChartContainer config={chartConfig} className="mx-auto aspect-square max-h-[250px]">
|
||||
<RadialBarChart
|
||||
data={chartData}
|
||||
startAngle={0}
|
||||
endAngle={250}
|
||||
innerRadius={80}
|
||||
outerRadius={110}
|
||||
>
|
||||
<PolarGrid
|
||||
gridType="circle"
|
||||
radialLines={false}
|
||||
stroke="none"
|
||||
className="first:fill-muted last:fill-background"
|
||||
polarRadius={[86, 74]}
|
||||
/>
|
||||
<RadialBar dataKey="visitors" background cornerRadius={10} />
|
||||
<PolarRadiusAxis tick={false} tickLine={false} axisLine={false}>
|
||||
<Label
|
||||
content={({ viewBox }) => {
|
||||
if (viewBox && 'cx' in viewBox && 'cy' in viewBox) {
|
||||
return (
|
||||
<text
|
||||
x={viewBox.cx}
|
||||
y={viewBox.cy}
|
||||
textAnchor="middle"
|
||||
dominantBaseline="middle"
|
||||
>
|
||||
<tspan
|
||||
x={viewBox.cx}
|
||||
y={viewBox.cy}
|
||||
className="fill-foreground text-4xl font-bold"
|
||||
>
|
||||
{chartData[0].visitors.toLocaleString()}
|
||||
</tspan>
|
||||
<tspan
|
||||
x={viewBox.cx}
|
||||
y={(viewBox.cy || 0) + 24}
|
||||
className="fill-muted-foreground"
|
||||
>
|
||||
Visitors
|
||||
</tspan>
|
||||
</text>
|
||||
)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</PolarRadiusAxis>
|
||||
</RadialBarChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="flex items-center gap-2 font-medium leading-none">
|
||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
||||
</div>
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Showing total visitors for the last 6 months
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -4,7 +4,7 @@ import { SourcePanel } from '@/components/source-panel'
|
||||
import { DashboardTableOfContents } from '@/components/toc'
|
||||
import { siteConfig } from '@/config/site'
|
||||
import { getTableOfContents } from '@/lib/toc'
|
||||
import { absoluteUrl, cn } from '@/lib/utils'
|
||||
import { absoluteUrl } from '@/lib/utils'
|
||||
import '@/styles/code-block-variables.css'
|
||||
import '@/styles/mdx.css'
|
||||
import { allDocs } from 'contentlayer/generated'
|
||||
@@ -83,15 +83,15 @@ export default async function DocPage(props: DocPageProps) {
|
||||
const toc = await getTableOfContents(doc.body.raw)
|
||||
|
||||
return (
|
||||
<main className="relative lg:gap-10 xl:grid xl:grid-cols-[1fr_200px] pr-6 lg:py-8">
|
||||
<div className="mx-auto w-full min-w-0 max-w-4xl">
|
||||
<div className="relative xl:grid xl:grid-cols-[1fr_160px] gap-4 px-6 py-6 lg:py-8">
|
||||
<div className="mx-auto w-full min-w-0 max-w-4xl flex-1">
|
||||
<div className="mb-4 flex items-center space-x-1 text-sm text-foreground-muted">
|
||||
<div className="overflow-hidden text-ellipsis whitespace-nowrap">Docs</div>
|
||||
<ChevronRight className="h-4 w-4 text-foreground-muted" />
|
||||
<div className="text-foreground-lighter">{doc.title}</div>
|
||||
</div>
|
||||
<div className="space-y-2 mb-5">
|
||||
<h1 className={cn('scroll-m-20 text-4xl tracking-tight')}>{doc.title}</h1>
|
||||
<h1 className="scroll-m-20 text-4xl tracking-tight">{doc.title}</h1>
|
||||
{doc.description && (
|
||||
<p className="text-lg text-foreground-light">
|
||||
<Balancer>{doc.description}</Balancer>
|
||||
@@ -107,15 +107,15 @@ export default async function DocPage(props: DocPageProps) {
|
||||
</div>
|
||||
{doc.toc && (
|
||||
<div className="hidden text-sm xl:block">
|
||||
<div className="sticky top-16 -mt-10 pt-4">
|
||||
<div className="sticky top-8 -mt-10 pt-8">
|
||||
<ScrollArea className="pb-10">
|
||||
<div className="sticky top-16 -mt-10 h-[calc(100vh-3.5rem)] py-12">
|
||||
<div className="h-[calc(100vh-3.5rem)] py-14">
|
||||
<DashboardTableOfContents toc={toc} />
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,34 +1,28 @@
|
||||
import SideNavigation from '@/components/side-navigation'
|
||||
import { SideNavigation } from '@/components/side-navigation'
|
||||
import { SiteFooter } from '@/components/site-footer'
|
||||
// import ThemeSettings from '@/components/theme-settings'
|
||||
import TopNavigation from '@/components/top-navigation'
|
||||
import { TopNavigation } from '@/components/top-navigation'
|
||||
import { MobileSidebarSheet } from '@/components/mobile-sidebar-sheet'
|
||||
import { ScrollArea } from 'ui'
|
||||
|
||||
interface AppLayoutProps {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export default function AppLayout({ children }: AppLayoutProps) {
|
||||
export default async function AppLayout({ children }: AppLayoutProps) {
|
||||
return (
|
||||
<>
|
||||
<TopNavigation />
|
||||
{/* main container */}
|
||||
<div className="">
|
||||
{/* main content */}
|
||||
<main className="flex-1 max-w-site mx-auto w-full border-l border-r">
|
||||
{/* {children} */}
|
||||
<div className="border-b">
|
||||
<div className="flex-1 items-start md:grid md:grid-cols-[220px_minmax(0,1fr)] md:gap-6 lg:grid-cols-[240px_minmax(0,1fr)] lg:gap-10">
|
||||
<aside className="fixed top-10 z-30 hidden h-[calc(100vh-3rem)] w-full shrink-0 md:sticky md:block border-r">
|
||||
<ScrollArea className="h-full py-6 lg:py-8">
|
||||
<SideNavigation />
|
||||
</ScrollArea>
|
||||
</aside>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<MobileSidebarSheet />
|
||||
<main className="flex-1 max-w-site mx-auto w-full border-l border-r border-b">
|
||||
<div className="flex-1 items-start md:grid md:grid-cols-[220px_minmax(0,1fr)] lg:grid-cols-[240px_minmax(0,1fr)]">
|
||||
<aside className="fixed top-10 z-30 hidden h-[calc(100vh-3rem)] w-full shrink-0 md:sticky md:block border-r">
|
||||
<ScrollArea className="h-full">
|
||||
<SideNavigation />
|
||||
</ScrollArea>
|
||||
</aside>
|
||||
{children}
|
||||
</div>
|
||||
</main>
|
||||
<SiteFooter />
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -1,114 +1,96 @@
|
||||
import { HomepageSvgHandler } from '@/components/homepage-svg-handler'
|
||||
import { Auth, Database, Realtime } from 'icons/src/icons'
|
||||
import { Paintbrush } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<main className="relative lg:gap-10 pr-6 lg:py-20">
|
||||
<div className="mx-auto w-full min-w-0 max-w-6xl flex flex-col gap-20">
|
||||
<div className="flex flex-col gap-8 justify-start">
|
||||
{/* <div>
|
||||
<DesignSystemMarks />
|
||||
</div> */}
|
||||
<div>
|
||||
<h1 className="text-4xl text-foreground mb-3">Supabase Design System</h1>
|
||||
<h2 className="text-xl text-foreground-light font-light">
|
||||
Design resources for building consistent user experiences
|
||||
</h2>
|
||||
</div>
|
||||
<div className="relative gap-4 px-6 py-6 lg:py-8">
|
||||
<div className="mx-auto w-full min-w-0 max-w-4xl flex-1">
|
||||
<div className="flex flex-col gap-2 justify-start my-8 w-full">
|
||||
<h1 className="text-2xl lg:text-4xl text-foreground">Supabase Design System</h1>
|
||||
<h2 className="md:text-xl text-base text-foreground-light font-light">
|
||||
Design resources for building consistent user experiences
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
{/* Homepage items */}
|
||||
|
||||
<div className="grid grid-cols-2 gap-10">
|
||||
<Link href="/docs/components/atom-components">
|
||||
<div className="px-10 py-8 min-h-[18rem] flex flex-col justify-between bg-surface-75/50 hover:bg-overlay/50 hover:border-foreground-muted cursor-pointer transition-all border rounded-md">
|
||||
<HomepageSvgHandler name="atoms-illustration" />
|
||||
<div>
|
||||
<h3 className="font-medium text-foreground">Atom components</h3>
|
||||
<p className="text-sm text-foreground-light">Building blocks of User interfaces</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
<Link href="/docs/components/fragment-components">
|
||||
<div className="px-10 py-8 min-h-[18rem] flex flex-col justify-between bg-surface-75/50 hover:bg-overlay/50 hover:border-foreground-muted cursor-pointer transition-all border rounded-md">
|
||||
<HomepageSvgHandler name="fragments-illustration" />
|
||||
<div>
|
||||
<h3 className="font-medium text-foreground">Fragment components</h3>
|
||||
<p className="text-sm text-foreground-light">Components assembled from Atoms</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
<Link href="/docs/color-usage">
|
||||
<div className="px-10 py-8 min-h-[18rem] flex flex-col justify-between bg-surface-75/50 hover:bg-overlay/50 hover:border-foreground-muted cursor-pointer transition-all border rounded-md">
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex flex-row gap-0.5">
|
||||
<div className="w-3 h-16 rounded border bg-foreground"></div>
|
||||
<div className="w-3 h-16 rounded border bg-foreground-light"></div>
|
||||
<div className="w-3 h-16 rounded border bg-foreground-lighter"></div>
|
||||
<div className="w-3 h-16 rounded border bg-foreground-muted"></div>
|
||||
<div className="w-3 h-16 rounded border"></div>
|
||||
|
||||
<div className="w-3 h-16 rounded border bg"></div>
|
||||
<div className="w-3 h-16 rounded border bg-200"></div>
|
||||
<div className="w-3 h-16 rounded border bg-surface-75"></div>
|
||||
<div className="w-3 h-16 rounded border bg-surface-100"></div>
|
||||
<div className="w-3 h-16 rounded border bg-surface-200"></div>
|
||||
<div className="w-3 h-16 rounded border bg-surface-300"></div>
|
||||
<div className="w-3 h-16 rounded border"></div>
|
||||
|
||||
<div className="w-3 h-16 rounded border bg-brand-200"></div>
|
||||
<div className="w-3 h-16 rounded border bg-brand-300"></div>
|
||||
<div className="w-3 h-16 rounded border bg-brand-400"></div>
|
||||
<div className="w-3 h-16 rounded border bg-brand-500"></div>
|
||||
<div className="w-3 h-16 rounded border bg-brand"></div>
|
||||
<div className="w-3 h-16 rounded border bg-brand-600"></div>
|
||||
<div className="w-3 h-16 rounded border"></div>
|
||||
|
||||
<div className="w-3 h-16 rounded border bg-warning-200"></div>
|
||||
<div className="w-3 h-16 rounded border bg-warning-300"></div>
|
||||
<div className="w-3 h-16 rounded border bg-warning-400"></div>
|
||||
<div className="w-3 h-16 rounded border bg-warning-500"></div>
|
||||
<div className="w-3 h-16 rounded border bg-warning"></div>
|
||||
<div className="w-3 h-16 rounded border bg-warning-600"></div>
|
||||
<div className="w-3 h-16 rounded border"></div>
|
||||
|
||||
<div className="w-3 h-16 rounded border bg-destructive-200"></div>
|
||||
<div className="w-3 h-16 rounded border bg-destructive-300"></div>
|
||||
<div className="w-3 h-16 rounded border bg-destructive-400"></div>
|
||||
<div className="w-3 h-16 rounded border bg-destructive-500"></div>
|
||||
<div className="w-3 h-16 rounded border bg-destructive"></div>
|
||||
<div className="w-3 h-16 rounded border bg-destructive-600"></div>
|
||||
<div className="w-3 h-16 rounded border"></div>
|
||||
</div>
|
||||
<div className="grid md:grid-cols-2 grid-cols-1 gap-4 w-full">
|
||||
<Link href="/docs/color-usage" className="h-full flex">
|
||||
<div className="p-6 gap-4 flex flex-col justify-between h-full w-full bg-surface-75 hover:bg-overlay/50 hover:border-foreground-muted cursor-pointer transition-all border rounded-md">
|
||||
<div className="flex flex-col gap-2 min-h-[24px]">
|
||||
<HomepageSvgHandler name="colours" className="w-6 h-6" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium text-foreground">Colors</h3>
|
||||
<p className="text-sm text-foreground-light">Building blocks of User interfaces</p>
|
||||
<p className="text-sm text-foreground-light">Custom color palette for Supabase</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<Link href="/docs/theming">
|
||||
<div className="px-10 py-8 min-h-[18rem] flex flex-col justify-between bg-surface-75/50 hover:bg-overlay/50 hover:border-foreground-muted cursor-pointer transition-all border rounded-md">
|
||||
<HomepageSvgHandler name="fragments-illustration" />
|
||||
<div>
|
||||
<h3 className="font-medium text-foreground">Theming</h3>
|
||||
<p className="text-sm text-foreground-light">Components assembled from Atoms</p>
|
||||
<Link href="/docs/icons" className="h-full flex">
|
||||
<div className="p-6 gap-4 flex flex-col justify-between h-full w-full bg-surface-75 hover:bg-overlay/50 hover:border-foreground-muted cursor-pointer transition-all border rounded-md">
|
||||
<div className="flex items-center justify-start min-h-[24px] gap-3 text-brand">
|
||||
<Realtime className="w-5 h-5" strokeWidth={1.5} stroke="currentColor" />
|
||||
<Database className="w-5 h-5 opacity-60" strokeWidth={1.5} stroke="currentColor" />
|
||||
<Auth className="w-5 h-5 opacity-30" strokeWidth={1.5} stroke="currentColor" />
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<Link href="/docs/icons">
|
||||
<div className="px-10 py-8 min-h-[18rem] flex flex-col justify-between bg-surface-75/50 hover:bg-overlay/50 hover:border-foreground-muted cursor-pointer transition-all border rounded-md">
|
||||
<HomepageSvgHandler name="fragments-illustration" />
|
||||
<div>
|
||||
<h3 className="font-medium text-foreground">Icons</h3>
|
||||
<p className="text-sm text-foreground-light">Components assembled from Atoms</p>
|
||||
<p className="text-sm text-foreground-light">Custom icons for Supabase</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<Link href="/docs/theming" className="h-full flex">
|
||||
<div className="p-6 gap-4 flex flex-col justify-between h-full w-full bg-surface-75 hover:bg-overlay/50 hover:border-foreground-muted cursor-pointer transition-all border rounded-md">
|
||||
<div className="flex items-center justify-start min-h-[24px] text-brand">
|
||||
<Paintbrush className="w-6 h-6" strokeWidth={1.5} stroke="currentColor" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium text-foreground">Theming</h3>
|
||||
<p className="text-sm text-foreground-light">Simple extensible theming system</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
<Link href="/docs/ui-patterns/introduction" className="h-full flex">
|
||||
<div className="p-6 gap-4 flex flex-col justify-between h-full w-full bg-surface-75 hover:bg-overlay/50 hover:border-foreground-muted cursor-pointer transition-all border rounded-md">
|
||||
<div className="flex items-center justify-start min-h-[24px]">
|
||||
<HomepageSvgHandler name="ui-patterns" className="w-6 h-6" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium text-foreground">UI patterns</h3>
|
||||
<p className="text-sm text-foreground-light">
|
||||
Design guidelines for common interface patterns
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<Link href="/docs/fragments/introduction" className="h-full flex">
|
||||
<div className="p-6 gap-4 flex flex-col justify-between h-full w-full bg-surface-75 hover:bg-overlay/50 hover:border-foreground-muted cursor-pointer transition-all border rounded-md">
|
||||
<div className="flex items-center justify-start min-h-[24px]">
|
||||
<HomepageSvgHandler name="fragments" className="w-6 h-6" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium text-foreground">Fragment components</h3>
|
||||
<p className="text-sm text-foreground-light">Components assembled from atoms</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<Link href="/docs/components/introduction" className="h-full flex">
|
||||
<div className="p-6 gap-4 flex flex-col justify-between h-full w-full bg-surface-75 hover:bg-overlay/50 hover:border-foreground-muted cursor-pointer transition-all border rounded-md">
|
||||
<div className="flex items-center justify-start min-h-[24px]">
|
||||
<HomepageSvgHandler name="atoms" className="w-6 h-6" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium text-foreground">Atom components</h3>
|
||||
<p className="text-sm text-foreground-light">Building blocks of user interfaces</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -5,12 +5,15 @@ import { ThemeProvider as NextThemesProvider } from 'next-themes'
|
||||
import { ThemeProviderProps } from 'next-themes/dist/types'
|
||||
|
||||
import { TooltipProvider } from 'ui'
|
||||
import { MobileSidebarProvider } from '@/context/mobile-sidebar-context'
|
||||
|
||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||
return (
|
||||
<JotaiProvider>
|
||||
<NextThemesProvider {...props}>
|
||||
<TooltipProvider delayDuration={0}>{children}</TooltipProvider>
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<MobileSidebarProvider>{children}</MobileSidebarProvider>
|
||||
</TooltipProvider>
|
||||
</NextThemesProvider>
|
||||
</JotaiProvider>
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { CircleIcon, LaptopIcon, MoonIcon, SunIcon } from 'lucide-react'
|
||||
import { CircleIcon, LaptopIcon, MoonIcon, SunIcon, Search } from 'lucide-react'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import * as React from 'react'
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
CommandList_Shadcn_,
|
||||
CommandSeparator_Shadcn_,
|
||||
DialogProps,
|
||||
DialogTitle,
|
||||
} from 'ui'
|
||||
|
||||
export function CommandMenu({ ...props }: DialogProps) {
|
||||
@@ -55,8 +56,7 @@ export function CommandMenu({ ...props }: DialogProps) {
|
||||
<Button
|
||||
type="outline"
|
||||
className={cn(
|
||||
`relative h-8 w-full justify-start rounded-[0.5rem] bg-background text-sm font-normal text-foreground-muted shadow-none sm:pr-12 md:w-40 lg:w-64
|
||||
hover:border-foreground-muted hover:bg-surface-100 hover:text-foreground-lighter
|
||||
`lg:flex hidden relative h-8 w-full justify-start rounded-[0.5rem] bg-background text-sm font-normal text-foreground-muted shadow-none sm:pr-12 md:w-40 lg:w-64 hover:border-foreground-muted hover:bg-surface-100 hover:text-foreground-lighter
|
||||
`
|
||||
)}
|
||||
onClick={() => setOpen(true)}
|
||||
@@ -68,26 +68,18 @@ export function CommandMenu({ ...props }: DialogProps) {
|
||||
<span className="text-sm">⌘</span>K
|
||||
</kbd>
|
||||
</Button>
|
||||
<Button
|
||||
type="text"
|
||||
size="tiny"
|
||||
className="px-1 group lg:hidden"
|
||||
onClick={() => setOpen(true)}
|
||||
icon={<Search size={16} />}
|
||||
/>
|
||||
<CommandDialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTitle className="sr-only">Search Design System...</DialogTitle>
|
||||
<CommandInput_Shadcn_ placeholder="Type a command or search..." />
|
||||
<CommandList_Shadcn_>
|
||||
<CommandEmpty_Shadcn_>No results found.</CommandEmpty_Shadcn_>
|
||||
{/* <CommandGroup_Shadcn_ heading="Links">
|
||||
{docsConfig.mainNav
|
||||
.filter((navitem) => !navitem.external)
|
||||
.map((navItem) => (
|
||||
<CommandItem_Shadcn_
|
||||
key={navItem.href}
|
||||
value={navItem.title}
|
||||
onSelect={() => {
|
||||
runCommand(() => router.push(navItem.href as string))
|
||||
}}
|
||||
>
|
||||
<FileIcon className="mr-2 h-4 w-4" strokeWidth={1} />
|
||||
{navItem.title}
|
||||
</CommandItem_Shadcn_>
|
||||
))}
|
||||
</CommandGroup_Shadcn_> */}
|
||||
{docsConfig.sidebarNav.map((group) => (
|
||||
<CommandGroup_Shadcn_ key={group.title} heading={group.title}>
|
||||
{group.items.map((navItem) => (
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import { Index } from '@/__registry__'
|
||||
import * as React from 'react'
|
||||
|
||||
import { useConfig } from '@/hooks/use-config'
|
||||
import { styles } from '@/registry/styles'
|
||||
import { ChevronRight, Expand } from 'lucide-react'
|
||||
import {
|
||||
Button,
|
||||
CollapsibleContent_Shadcn_,
|
||||
@@ -10,9 +13,6 @@ import {
|
||||
Collapsible_Shadcn_,
|
||||
cn,
|
||||
} from 'ui'
|
||||
import { useConfig } from '@/hooks/use-config'
|
||||
import { styles } from '@/registry/styles'
|
||||
import { ChevronRight, Expand } from 'lucide-react'
|
||||
|
||||
interface ComponentPreviewProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
name: string
|
||||
@@ -23,6 +23,7 @@ interface ComponentPreviewProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
showGrid?: boolean
|
||||
showDottedGrid?: boolean
|
||||
wide?: boolean
|
||||
hideCode?: boolean
|
||||
}
|
||||
|
||||
export function ComponentPreview({
|
||||
@@ -36,6 +37,7 @@ export function ComponentPreview({
|
||||
showGrid = false,
|
||||
showDottedGrid = true,
|
||||
wide = false,
|
||||
hideCode = false,
|
||||
...props
|
||||
}: ComponentPreviewProps) {
|
||||
const [config] = useConfig()
|
||||
@@ -68,7 +70,7 @@ export function ComponentPreview({
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={cn('preview flex min-h-[350px] w-full justify-center p-10', {
|
||||
className={cn('preview flex min-h-[256px] w-full justify-center p-10', {
|
||||
'items-center': align === 'center',
|
||||
'items-start': align === 'start',
|
||||
'items-end': align === 'end',
|
||||
@@ -136,7 +138,10 @@ export function ComponentPreview({
|
||||
return (
|
||||
<div className={cn('mt-4 mb-12', wideClasses)}>
|
||||
<div
|
||||
className={cn('relative rounded-tl-md rounded-tr-md border-t border-l border-r bg-studio')}
|
||||
className={cn('relative bg-studio', {
|
||||
'rounded-tl-md rounded-tr-md border-t border-l border-r': !hideCode,
|
||||
'rounded-md border': hideCode,
|
||||
})}
|
||||
>
|
||||
{showGrid && (
|
||||
<div className="pointer-events-none absolute h-full w-full bg-[linear-gradient(to_right,hsla(var(--foreground-default)/0.02)_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:24px_24px]"></div>
|
||||
@@ -146,42 +151,44 @@ export function ComponentPreview({
|
||||
)}
|
||||
<div className="z-10 relative">{ComponentPreview}</div>
|
||||
</div>
|
||||
<Collapsible_Shadcn_>
|
||||
<CollapsibleTrigger_Shadcn_
|
||||
className={`
|
||||
flex
|
||||
gap-3 items-center
|
||||
w-full
|
||||
font-mono
|
||||
text-xs
|
||||
text-foreground-light
|
||||
px-4 py-4
|
||||
border border-r
|
||||
group
|
||||
data-[state=closed]:rounded-bl-md data-[state=closed]:rounded-br-md
|
||||
|
||||
`}
|
||||
>
|
||||
<ChevronRight
|
||||
className="transition-all group-data-[state=open]:rotate-90 text-foreground-lighter"
|
||||
size={14}
|
||||
/>
|
||||
View code
|
||||
</CollapsibleTrigger_Shadcn_>
|
||||
<CollapsibleContent_Shadcn_ className="transition-all">
|
||||
<div
|
||||
className={cn(
|
||||
'relative',
|
||||
'w-full rounded-md [&_pre]:my-0',
|
||||
'[&_pre]:overflow-auto',
|
||||
'[&_pre]:max-h-[320px]',
|
||||
'[&_pre]:rounded-tr-none [&_pre]:rounded-tl-none [&_pre]:border-t-transparent'
|
||||
)}
|
||||
{!hideCode && (
|
||||
<Collapsible_Shadcn_>
|
||||
<CollapsibleTrigger_Shadcn_
|
||||
className={`
|
||||
flex
|
||||
gap-3 items-center
|
||||
w-full
|
||||
font-mono
|
||||
text-xs
|
||||
text-foreground-light
|
||||
px-4 py-4
|
||||
border border-r
|
||||
group
|
||||
data-[state=closed]:rounded-bl-md data-[state=closed]:rounded-br-md
|
||||
|
||||
`}
|
||||
>
|
||||
{Code}
|
||||
</div>
|
||||
</CollapsibleContent_Shadcn_>
|
||||
</Collapsible_Shadcn_>
|
||||
<ChevronRight
|
||||
className="transition-all group-data-[state=open]:rotate-90 text-foreground-lighter"
|
||||
size={14}
|
||||
/>
|
||||
View code
|
||||
</CollapsibleTrigger_Shadcn_>
|
||||
<CollapsibleContent_Shadcn_ className="transition-all">
|
||||
<div
|
||||
className={cn(
|
||||
'relative',
|
||||
'w-full rounded-md [&_pre]:my-0',
|
||||
'[&_pre]:overflow-auto',
|
||||
'[&_pre]:max-h-[320px]',
|
||||
'[&_pre]:rounded-tr-none [&_pre]:rounded-tl-none [&_pre]:border-t-transparent'
|
||||
)}
|
||||
>
|
||||
{Code}
|
||||
</div>
|
||||
</CollapsibleContent_Shadcn_>
|
||||
</Collapsible_Shadcn_>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
import fs from 'fs'
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from 'ui'
|
||||
// import { parse } from 'react-docgen'
|
||||
|
||||
export function ComponentProps(props: any) {
|
||||
// map through all props types for this component DropdownMenu
|
||||
@@ -11,10 +9,6 @@ export function ComponentProps(props: any) {
|
||||
// export default ({ name }: { name: string }) => <div>{{name}}</div>;
|
||||
// `
|
||||
|
||||
// const documentation = parse(code)
|
||||
//
|
||||
// console.log(documentation)
|
||||
|
||||
// console.log('from the component props', JSON.parse(props.docs))
|
||||
|
||||
const docs = JSON.parse(props.docs)
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
'use client'
|
||||
|
||||
import { SideNavigation } from '@/components/side-navigation'
|
||||
import { useMobileSidebar } from '@/hooks/use-mobile-sidebar'
|
||||
import { Sheet, SheetContent, SheetTitle, ScrollArea } from 'ui'
|
||||
|
||||
export function MobileSidebarSheet() {
|
||||
const { open, setOpen } = useMobileSidebar()
|
||||
|
||||
return (
|
||||
<Sheet open={open} onOpenChange={setOpen}>
|
||||
<SheetContent side="left" className="p-0 w-[240px] sm:w-[280px]" showClose={false}>
|
||||
<SheetTitle className="sr-only">Navigation</SheetTitle>
|
||||
<ScrollArea className="h-full py-6 lg:py-8">
|
||||
<SideNavigation />
|
||||
</ScrollArea>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
)
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import { NavItem, NavItemWithChildren } from '@/types/nav'
|
||||
|
||||
import { docsConfig } from '@/config/docs'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { buttonVariants } from 'ui'
|
||||
import { ChevronLeft, ChevronRight } from 'lucide-react'
|
||||
|
||||
interface DocsPagerProps {
|
||||
@@ -18,8 +17,10 @@ export function DocsPager({ doc }: DocsPagerProps) {
|
||||
return null
|
||||
}
|
||||
|
||||
const hasBoth = pager?.prev?.href && pager?.next?.href
|
||||
|
||||
return (
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<div className={cn('flex flex-row items-center', hasBoth ? 'justify-between' : 'justify-end')}>
|
||||
{pager?.prev?.href && (
|
||||
<Link
|
||||
href={pager.prev.href}
|
||||
|
||||
@@ -5,15 +5,22 @@ import { cn } from 'ui/src/lib/utils/cn'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
import { useMobileSidebar } from '@/hooks/use-mobile-sidebar'
|
||||
|
||||
const NavigationItem: React.FC<{ item: SidebarNavItem }> = React.memo(({ item }) => {
|
||||
export const NavigationItem: React.FC<{ item: SidebarNavItem }> = React.memo(({ item }) => {
|
||||
const pathname = usePathname()
|
||||
const { setOpen } = useMobileSidebar()
|
||||
|
||||
const isActive = pathname === item.href
|
||||
|
||||
const handleClick = () => {
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={`${item.href}`}
|
||||
onClick={handleClick}
|
||||
className={cn(
|
||||
'relative',
|
||||
'flex',
|
||||
@@ -40,5 +47,3 @@ const NavigationItem: React.FC<{ item: SidebarNavItem }> = React.memo(({ item })
|
||||
})
|
||||
|
||||
NavigationItem.displayName = 'NavigationItem'
|
||||
|
||||
export default NavigationItem
|
||||
@@ -1,15 +1,24 @@
|
||||
import { NavigationItem } from '@/components/side-navigation-item'
|
||||
import { docsConfig } from '@/config/docs'
|
||||
import NavigationItem from '@/components/side-navigation-item'
|
||||
|
||||
function SideNavigation() {
|
||||
export const SideNavigation = () => {
|
||||
return (
|
||||
<nav className="min-w-[220px]">
|
||||
<nav className="min-w-[220px] py-6 lg:py-8">
|
||||
{docsConfig.sidebarNav.map((section, i) => (
|
||||
<div key={`${section.title}-${i}`} className="pb-10 space-y-0.5">
|
||||
<div className="font-mono uppercase text-xs text-foreground-lighter/75 mb-2 px-6 tracking-widest">
|
||||
{section.title}
|
||||
</div>
|
||||
{section.items.map((item, i) => (
|
||||
{(section.sortOrder === 'alphabetical'
|
||||
? (() => {
|
||||
const priorityItems = section.items.filter((item) => item.priority)
|
||||
const regularItems = section.items
|
||||
.filter((item) => !item.priority)
|
||||
.sort((a, b) => a.title.localeCompare(b.title))
|
||||
return [...priorityItems, ...regularItems]
|
||||
})()
|
||||
: section.items
|
||||
).map((item, i) => (
|
||||
<NavigationItem item={item} key={`${item.href}-${i}`} />
|
||||
))}
|
||||
</div>
|
||||
@@ -17,5 +26,3 @@ function SideNavigation() {
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
||||
export default SideNavigation
|
||||
@@ -2,8 +2,8 @@ import { siteConfig } from '@/config/site'
|
||||
|
||||
export function SiteFooter() {
|
||||
return (
|
||||
<footer className="py-6 md:px-8 md:py-0 mx-auto border-r border-l border-b w-full max-w-site">
|
||||
<div className="flex flex-col items-center justify-between gap-4 md:h-24 md:flex-row">
|
||||
<footer className="py-6 px-6 mx-auto border-r border-l border-b w-full max-w-site">
|
||||
<div className="flex flex-col items-center justify-between gap-4 md:flex-row">
|
||||
<p className="text-balance text-center text-sm leading-loose text-foreground-muted md:text-left">
|
||||
Built by{' '}
|
||||
<a
|
||||
|
||||
@@ -240,12 +240,70 @@ const SourcePanel = forwardRef<HTMLDivElement, React.HTMLProps<HTMLDivElement> &
|
||||
}
|
||||
}
|
||||
|
||||
const RechartsPanel = () => {
|
||||
if (doc.source?.recharts) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'bg-surface-75/50 border flex items-center p-3 px-5 gap-6 first:rounded-t-md last:rounded-b-md',
|
||||
props.className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<svg
|
||||
width="146"
|
||||
height="24"
|
||||
viewBox="0 0 146 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M124.123 1.21484L109.695 23.9951L108.737 23.3887L107.779 22.7812L122.207 0L124.123 1.21484ZM15.4844 5.4082L2.96387 11.998L15.4844 18.5879L14.4277 20.5957L0 13.002L0.52832 11.998L0 10.9941L14.4277 3.40039L15.4844 5.4082ZM145.335 10.9941L144.807 11.998L145.335 13.002L130.907 20.5957L129.851 18.5879L142.37 11.998L129.851 5.4082L130.907 3.40039L145.335 10.9941ZM68.5801 8.39746C68.8994 8.39746 69.2665 8.43699 69.6807 8.51465C70.0991 8.58798 70.5024 8.74074 70.8906 8.97363C71.2832 9.20658 71.6093 9.55816 71.8682 10.0283C72.127 10.4985 72.2568 11.1286 72.2568 11.918V18.4678H70.7285V17.1221H70.6514C70.6409 17.1439 70.6271 17.1644 70.6152 17.1865C70.598 17.2186 70.5818 17.2524 70.5615 17.2852L70.4443 17.457C70.3581 17.5724 70.2545 17.6916 70.1338 17.8145C70.1245 17.8239 70.1141 17.8324 70.1045 17.8418C70.0734 17.8722 70.0414 17.9023 70.0078 17.9316C69.7839 18.1272 69.5044 18.2981 69.1689 18.4424C68.7678 18.6148 68.2781 18.7012 67.7002 18.7012C67.0703 18.7012 66.4988 18.582 65.9854 18.3447C65.6861 18.2039 65.4237 18.0261 65.1963 17.8135C65.0335 17.6615 64.8876 17.4915 64.7617 17.3027C64.5935 17.0479 64.4739 16.763 64.3994 16.4492C64.3724 16.3357 64.3497 16.2188 64.335 16.0977C64.3344 16.0928 64.3346 16.0879 64.334 16.083C64.3175 15.9423 64.3086 15.7965 64.3086 15.6455C64.3087 15.0766 64.4215 14.6153 64.6455 14.2617C64.7017 14.172 64.7624 14.0869 64.8281 14.0068C64.8444 13.987 64.862 13.9684 64.8789 13.9492C65.0657 13.7362 65.287 13.5583 65.5449 13.4189C65.736 13.3157 65.938 13.2267 66.1494 13.1504C66.3529 13.0768 66.5659 13.0155 66.7881 12.9668C66.9022 12.941 67.0165 12.9167 67.1309 12.8936C67.2457 12.8703 67.3605 12.8477 67.4756 12.8271C67.5904 12.8067 67.7053 12.7873 67.8203 12.7695C67.9354 12.7517 68.0507 12.7358 68.166 12.7207C68.77 12.6431 69.2604 12.5847 69.6357 12.5459C70.0151 12.5027 70.2914 12.4312 70.4639 12.332C70.53 12.2949 70.5836 12.2471 70.625 12.1895C70.6389 12.1701 70.651 12.1496 70.6621 12.1279C70.7063 12.0417 70.7285 11.9374 70.7285 11.8145V11.7627C70.7285 11.4437 70.6849 11.1601 70.5977 10.9121C70.554 10.7881 70.5001 10.6726 70.4346 10.5664C70.3692 10.4603 70.2923 10.3628 70.2051 10.2744C70.0325 10.0977 69.8149 9.9654 69.5527 9.87695C69.4871 9.85481 69.4189 9.83496 69.3477 9.81836C69.1342 9.76858 68.8955 9.74415 68.6318 9.74414C67.9029 9.7442 67.3311 9.90344 66.917 10.2227C66.7617 10.3424 66.6236 10.4651 66.5029 10.5908C66.3458 10.7544 66.2187 10.9237 66.1201 11.0977C66.1177 11.102 66.1147 11.106 66.1123 11.1104L66.0439 11.2451L64.5928 10.7275C64.6561 10.5797 64.7251 10.4395 64.7988 10.3076C64.8087 10.29 64.82 10.2732 64.8301 10.2559C64.8959 10.1423 64.9653 10.0349 65.0391 9.93359C65.0466 9.9232 65.0539 9.91261 65.0615 9.90234C65.0956 9.85658 65.1312 9.81272 65.167 9.76953C65.1741 9.76094 65.1803 9.75165 65.1875 9.74316C65.2256 9.69808 65.2647 9.65451 65.3047 9.6123C65.312 9.6046 65.3188 9.59647 65.3262 9.58887C65.3732 9.54019 65.4221 9.49397 65.4717 9.44922L65.5156 9.41211C65.5531 9.37961 65.59 9.34673 65.6289 9.31641C66.0646 8.97561 66.5394 8.73826 67.0527 8.60449C67.5703 8.46646 68.0797 8.3975 68.5801 8.39746ZM37.4268 8.40137C37.491 8.40408 37.5551 8.40769 37.6191 8.41309C38.0052 8.44542 38.3878 8.52676 38.7666 8.65625C38.8269 8.67688 38.8863 8.69992 38.9453 8.72363C39.0099 8.74954 39.0737 8.77707 39.1367 8.80664C39.4982 8.97651 39.835 9.20692 40.1455 9.49805C40.5595 9.88199 40.8889 10.3914 41.1348 11.0254C41.3807 11.6596 41.5039 12.4405 41.5039 13.3682V14.0156H34.2861C34.2975 14.3591 34.3374 14.677 34.4062 14.9697C34.4355 15.0944 34.4691 15.2146 34.5088 15.3301C34.5156 15.3499 34.5241 15.3691 34.5312 15.3887C34.5726 15.5024 34.6181 15.6123 34.6699 15.7168C34.7686 15.9108 34.8817 16.0874 35.0088 16.2471C35.2209 16.5132 35.4723 16.7314 35.7637 16.9014C35.7835 16.9128 35.8041 16.9227 35.8242 16.9336C35.9066 16.9783 35.9911 17.0191 36.0781 17.0557C36.4686 17.2196 36.9047 17.3027 37.3877 17.3027C37.7888 17.3027 38.1511 17.2468 38.4746 17.1348C38.7205 17.0474 38.9408 16.9278 39.1357 16.7773C39.2006 16.7273 39.263 16.6742 39.3223 16.6172C39.5596 16.3842 39.7426 16.0942 39.8721 15.749L41.3486 16.1641C41.2641 16.4361 41.147 16.6903 41 16.9268C40.8797 17.1202 40.7393 17.3022 40.5771 17.4717C40.5732 17.4758 40.5694 17.4802 40.5654 17.4844C40.4739 17.578 40.3767 17.6666 40.2744 17.75C40.1718 17.8337 40.0636 17.9119 39.9502 17.9854C39.7239 18.1318 39.4764 18.2586 39.207 18.3643C39.1943 18.3692 39.1808 18.3731 39.168 18.3779C39.0606 18.418 38.9505 18.4534 38.8379 18.4854C38.7505 18.5102 38.6618 18.5338 38.5713 18.5537C38.2032 18.6345 37.809 18.6757 37.3887 18.6758C36.9098 18.6758 36.4631 18.6224 36.0498 18.5166C35.6367 18.4109 35.2556 18.2524 34.9082 18.041C34.2182 17.6139 33.6858 17.0182 33.3105 16.2549C33.0626 15.7416 32.8996 15.1719 32.8174 14.5469C32.7764 14.2366 32.7539 13.9129 32.7539 13.5752C32.7539 13.0662 32.8006 12.5872 32.8936 12.1387C32.9168 12.0264 32.9427 11.9161 32.9717 11.8076C33.005 11.6833 33.043 11.5618 33.084 11.4424C33.15 11.2496 33.2246 11.0627 33.3105 10.8828C33.53Line truncated
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<div className="flex flex-row items-center justify-between text-sm w-full">
|
||||
<span className="text-foreground-light text-xs">This component uses Recharts</span>
|
||||
{doc.links ? (
|
||||
<div className="flex items-center gap-2 justify-end">
|
||||
{doc.links?.doc && (
|
||||
<Button
|
||||
type="outline"
|
||||
className="rounded-full"
|
||||
icon={<ExternalLink className="text-foreground-muted" strokeWidth={1} />}
|
||||
>
|
||||
<Link href={doc.links.doc} target="_blank" rel="noreferrer">
|
||||
Docs
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
{doc.links?.api && (
|
||||
<Button
|
||||
type="outline"
|
||||
className="rounded-full"
|
||||
icon={<ExternalLink className="text-foreground-muted" strokeWidth={1} />}
|
||||
>
|
||||
<Link href={doc.links.api} target="_blank" rel="noreferrer">
|
||||
API Reference
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col -space-y-px">
|
||||
<RadixPanel />
|
||||
<VaulPanel />
|
||||
<InputOtp />
|
||||
<ReactAccesibleTreeViewPanel />
|
||||
<RechartsPanel />
|
||||
{/* <ShadcnPanel /> */}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { Moon, Sun } from 'lucide-react'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { useEffect, useState } from 'react'
|
||||
import SVG from 'react-inlinesvg'
|
||||
import {
|
||||
Button,
|
||||
DropdownMenu,
|
||||
@@ -13,8 +12,6 @@ import {
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
RadioGroupLargeItem_Shadcn_,
|
||||
RadioGroup_Shadcn_,
|
||||
Theme,
|
||||
singleThemes,
|
||||
} from 'ui'
|
||||
@@ -36,22 +33,7 @@ const ThemeSwitcherDropdown = () => {
|
||||
return null
|
||||
}
|
||||
|
||||
function SingleThemeSelection() {
|
||||
return (
|
||||
<form>
|
||||
<RadioGroup_Shadcn_
|
||||
name="theme"
|
||||
onValueChange={setTheme}
|
||||
aria-label="Choose a theme"
|
||||
defaultValue={theme}
|
||||
value={theme}
|
||||
className="flex flex-wrap gap-3"
|
||||
></RadioGroup_Shadcn_>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
const iconClasses = 'text-foreground-light group-data-[state=open]:text-foreground'
|
||||
const iconClasses = 'text-foreground-lighter group-data-[state=open]:text-foreground'
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -68,7 +50,7 @@ const ThemeSwitcherDropdown = () => {
|
||||
<Moon className={iconClasses} />
|
||||
)
|
||||
}
|
||||
></Button>
|
||||
/>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-56" align="end">
|
||||
<DropdownMenuLabel>Theme</DropdownMenuLabel>
|
||||
|
||||
@@ -1,30 +1,37 @@
|
||||
// import { docsConfig } from '@/config/docs'
|
||||
'use client'
|
||||
|
||||
import Link from 'next/link'
|
||||
import { Menu } from 'lucide-react'
|
||||
import { Button } from 'ui'
|
||||
import { CommandMenu } from './command-menu'
|
||||
import { HomepageSvgHandler } from './homepage-svg-handler'
|
||||
import { ThemeSwitcherDropdown } from './theme-switcher-dropdown'
|
||||
import { useMobileSidebar } from '@/hooks/use-mobile-sidebar'
|
||||
|
||||
export const TopNavigation = () => {
|
||||
const { toggle } = useMobileSidebar()
|
||||
|
||||
function TopNavigation() {
|
||||
return (
|
||||
<header className="sticky top-0 z-50 w-full border-t bg-studio/95 backdrop-blur supports-[backdrop-filter]:bg-studio/60">
|
||||
<div className="absolute border-b border-dashed w-full top-[3.4rem] -z-10"></div>
|
||||
<nav className="h-14 w-full flex">
|
||||
<div className="max-w-site border-b w-full flex flex-row items-center gap-6 mx-auto px-6 border-r border-l justify-between">
|
||||
<div className="flex items-center gap-8">
|
||||
<Link href="/">
|
||||
<h1>Supabase Design System</h1>
|
||||
<header className="sticky top-0 z-50 w-full bg-studio/95 backdrop-blur supports-[backdrop-filter]:bg-studio/60 border-b border-l border-r">
|
||||
<nav className="py-3 w-full flex">
|
||||
<div className="max-w-site w-full flex flex-row items-center gap-6 mx-auto md:px-6 px-4 justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
type="text"
|
||||
size="tiny"
|
||||
className="px-1 md:hidden"
|
||||
onClick={toggle}
|
||||
icon={<Menu size={16} />}
|
||||
aria-label="Toggle navigation menu"
|
||||
/>
|
||||
<Link href="/" className="flex items-center lg:gap-6 gap-4">
|
||||
<h1 className="hidden md:flex line-clamp-1 lg:text-2xl text-xl">
|
||||
Supabase Design System
|
||||
</h1>
|
||||
<HomepageSvgHandler name="logo" className="h-4 w-auto" />
|
||||
</Link>
|
||||
<HomepageSvgHandler name="design-system-marks" className="h-4 w-auto" />
|
||||
</div>
|
||||
{/* {docsConfig.mainNav.map((section) => (
|
||||
<>
|
||||
<div className="font-mono uppercase text-xs text-foreground-lighter">
|
||||
{section.title}
|
||||
</div>
|
||||
</>
|
||||
))} */}
|
||||
<div className="flex items-center gap-8">
|
||||
{/* <TopNavigationSearch /> */}
|
||||
<div className="flex items-center gap-2">
|
||||
<CommandMenu />
|
||||
<ThemeSwitcherDropdown />
|
||||
</div>
|
||||
@@ -33,5 +40,3 @@ function TopNavigation() {
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
export default TopNavigation
|
||||
@@ -9,30 +9,28 @@ export const docsConfig: DocsConfig = {
|
||||
sidebarNav: [
|
||||
{
|
||||
title: 'Getting Started',
|
||||
sortOrder: 'alphabetical',
|
||||
items: [
|
||||
{
|
||||
title: 'Introduction',
|
||||
href: '/docs',
|
||||
priority: true,
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Tailwind Classes',
|
||||
href: '/docs/tailwind-classes',
|
||||
title: 'Accessibility',
|
||||
href: '/docs/accessibility',
|
||||
items: [],
|
||||
},
|
||||
|
||||
{
|
||||
title: 'Color Usage',
|
||||
href: '/docs/color-usage',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Typography',
|
||||
href: '/docs/typography',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Theming',
|
||||
href: '/docs/theming',
|
||||
title: 'Copywriting',
|
||||
href: '/docs/copywriting',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
@@ -41,23 +39,35 @@ export const docsConfig: DocsConfig = {
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Accessibility',
|
||||
href: '/docs/accessibility',
|
||||
title: 'Tailwind Classes',
|
||||
href: '/docs/tailwind-classes',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Theming',
|
||||
href: '/docs/theming',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Typography',
|
||||
href: '/docs/typography',
|
||||
items: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'UI Patterns',
|
||||
sortOrder: 'alphabetical',
|
||||
items: [
|
||||
{
|
||||
title: 'Navigation',
|
||||
href: '/docs/ui-patterns/navigation',
|
||||
title: 'Introduction',
|
||||
href: '/docs/ui-patterns/ui-patterns',
|
||||
items: [],
|
||||
priority: true,
|
||||
},
|
||||
{
|
||||
title: 'Layout',
|
||||
href: '/docs/ui-patterns/layout',
|
||||
title: 'Charts',
|
||||
href: '/docs/ui-patterns/charts',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
@@ -70,15 +80,37 @@ export const docsConfig: DocsConfig = {
|
||||
href: '/docs/ui-patterns/forms',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Layout',
|
||||
href: '/docs/ui-patterns/layout',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
href: '/docs/ui-patterns/modality',
|
||||
title: 'Modality',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
href: '/docs/ui-patterns/navigation',
|
||||
title: 'Navigation',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Tables',
|
||||
href: '/docs/ui-patterns/tables',
|
||||
items: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Fragment Components',
|
||||
sortOrder: 'alphabetical',
|
||||
items: [
|
||||
{
|
||||
title: 'Introduction',
|
||||
href: '/docs/components/fragment-components',
|
||||
href: '/docs/fragments/fragment-components',
|
||||
items: [],
|
||||
priority: true,
|
||||
},
|
||||
{
|
||||
title: 'Admonition',
|
||||
@@ -91,7 +123,7 @@ export const docsConfig: DocsConfig = {
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'EmptyStatePresentational',
|
||||
title: 'Empty State Presentational',
|
||||
href: '/docs/fragments/empty-state-presentational',
|
||||
items: [],
|
||||
},
|
||||
@@ -165,15 +197,22 @@ export const docsConfig: DocsConfig = {
|
||||
href: '/docs/fragments/toc',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Confirmation Modal',
|
||||
href: '/docs/fragments/confirmation-modal',
|
||||
items: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Atom Components',
|
||||
sortOrder: 'alphabetical',
|
||||
items: [
|
||||
{
|
||||
title: 'Introduction',
|
||||
href: '/docs/components/atom-components',
|
||||
items: [],
|
||||
priority: true,
|
||||
},
|
||||
{
|
||||
title: 'Accordion',
|
||||
@@ -231,11 +270,6 @@ export const docsConfig: DocsConfig = {
|
||||
href: '/docs/components/carousel',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Chart',
|
||||
href: '/docs/components/chart',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Checkbox',
|
||||
href: '/docs/components/checkbox',
|
||||
@@ -266,11 +300,6 @@ export const docsConfig: DocsConfig = {
|
||||
href: '/docs/components/context-menu',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Data Table',
|
||||
href: '/docs/components/data-table',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Date Picker',
|
||||
href: '/docs/components/date-picker',
|
||||
@@ -291,6 +320,11 @@ export const docsConfig: DocsConfig = {
|
||||
href: '/docs/components/dropdown-menu',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Field',
|
||||
href: '/docs/components/field',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Form',
|
||||
href: '/docs/components/form',
|
||||
@@ -329,7 +363,7 @@ export const docsConfig: DocsConfig = {
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'NavMenu',
|
||||
title: 'Nav Menu',
|
||||
href: '/docs/components/nav-menu',
|
||||
items: [],
|
||||
},
|
||||
|
||||
@@ -27,29 +27,33 @@ Chromium-based browsers and Firefox handle this automatically via the Tab key. S
|
||||
|
||||
Interactive page elements should also provide visual feedback upon selection via a `focus-visible` state. We use consistent focus styles such as `inset-focus` so users recognize this state instantly.
|
||||
|
||||
[Button](../components/button) has all of the above built-in. Bespoke interactive elements however, such as the below clickable [TableRow](../components/table), require these props to be added manually:
|
||||
[Button](components/button) has all of the above built-in. Bespoke interactive elements however, such as the below interactive [Table Row](components/table#examples), require these props to be added manually:
|
||||
|
||||
```tsx showLineNumbers {3, 5-11}
|
||||
```tsx showLineNumbers {4-14}
|
||||
<TableRow
|
||||
key={id}
|
||||
className="relative cursor-pointer h-16 inset-focus"
|
||||
onClick={(event) => handleBucketNavigation(name, event)}
|
||||
onClick={(event) => {
|
||||
if (event.currentTarget !== event.target) return
|
||||
handleBucketNavigation(bucket.id, event)
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (event.currentTarget !== event.target) return
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault()
|
||||
handleBucketNavigation(name, event)
|
||||
handleBucketNavigation(bucket.id, event)
|
||||
}
|
||||
}}
|
||||
tabIndex={0}
|
||||
>
|
||||
<TableCell>
|
||||
<p>{name}</p>
|
||||
</TableCell>
|
||||
<TableCell>{name}</TableCell>
|
||||
</TableRow>
|
||||
```
|
||||
|
||||
Consider also affordances like `ctrl` and `meta` key support for opening in a new tab. Anything that you can do with a mouse input should be replicable by keyboard.
|
||||
|
||||
See the examples within [Table](components/table#examples) for more.
|
||||
|
||||
### Radio groups
|
||||
|
||||
Single-select option groups such as `<input type="radio">` should behave as a single control. Only the first item of radio groups should become focused with the Tab key. The next Tab should move focus from the group to the next focusable control.
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
---
|
||||
title: Alert Dialog
|
||||
description: A modal dialog that interrupts the user with important content and expects a response.
|
||||
featured: true
|
||||
description: A modal dialog for critical confirmations and acknowledgements that require an explicit user decision.
|
||||
component: true
|
||||
links:
|
||||
doc: https://www.radix-ui.com/docs/primitives/components/alert-dialog
|
||||
api: https://www.radix-ui.com/docs/primitives/components/alert-dialog#api-reference
|
||||
source:
|
||||
radix: true
|
||||
shadcn: true
|
||||
radix: true
|
||||
---
|
||||
|
||||
Alert Dialog interrupts the user’s workflow to communicate critical information or confirm an action that cannot be taken lightly. It presents a short, focused message and requires the user to explicitly confirm or cancel before proceeding.
|
||||
|
||||
Use Alert Dialog for actions such as deleting data, performing irreversible changes, or acknowledging important warnings where dismissal without a decision would be unsafe.
|
||||
|
||||
<ComponentPreview name="alert-dialog-demo" peekCode wide />
|
||||
|
||||
## Installation
|
||||
@@ -85,3 +88,32 @@ import {
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
```
|
||||
|
||||
## Behavior
|
||||
|
||||
Unlike a generic [Dialog](../components/dialog), an Alert Dialog cannot be dismissed by clicking outside the modal. The user must take an explicit action by confirming, cancelling, or pressing Escape.
|
||||
|
||||
This enforced decision helps prevent accidental dismissal of critical warnings or destructive actions.
|
||||
|
||||
## Guidelines
|
||||
|
||||
- **Keep content concise:** AlertDialogDescription renders as a single paragraph and must not contain block-level elements such as lists, multiple paragraphs, or complex layouts.
|
||||
- **Use for critical decisions only:** Reserve Alert Dialog for destructive or irreversible actions, or for warnings that require explicit acknowledgement.
|
||||
- **Always provide a cancel action:** Include AlertDialogCancel so users can safely back out, in addition to supporting the Escape key.
|
||||
- **Avoid rich content:** If the dialog requires detailed explanations, callouts, or form inputs, use [Confirmation Modal](../fragments/confirmation-modal) or [Dialog](../components/dialog) instead.
|
||||
|
||||
See [Modality](../ui-patterns/modality) for guidance on choosing the appropriate dialog pattern.
|
||||
|
||||
## Examples
|
||||
|
||||
### Close only
|
||||
|
||||
<ComponentPreview name="alert-dialog-close-only" />
|
||||
|
||||
### Warning
|
||||
|
||||
<ComponentPreview name="alert-dialog-warning" />
|
||||
|
||||
### Destructive
|
||||
|
||||
<ComponentPreview name="alert-dialog-destructive" />
|
||||
@@ -7,47 +7,19 @@ source:
|
||||
shadcn: true
|
||||
---
|
||||
|
||||
The primitive component that forms [Admonition](../fragments/admonition). Do not use this component unless you are intentionally deviating from our consistent callout pattern.
|
||||
Alert forms the core primitives for [Admonition](../fragments/admonition). Use the Admonition component unless you must deviate from our consistent patterns. Examples where Alert might make sense include:
|
||||
|
||||
<ComponentPreview
|
||||
name="alert-demo"
|
||||
description="An alert with an icon, title and description. The title says 'Heads up!' and the description is 'You can add components to your app using the cli.'."
|
||||
peekCode
|
||||
wide
|
||||
/>
|
||||
- Bespoke icon requirement
|
||||
- Highly custom internal contents
|
||||
|
||||
<ComponentPreview name="alert-demo" peekCode wide />
|
||||
|
||||
## Installation
|
||||
|
||||
<Tabs defaultValue="cli">
|
||||
|
||||
<TabsList>
|
||||
<TabsTrigger value="cli">CLI</TabsTrigger>
|
||||
<TabsTrigger value="manual">Manual</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="cli">
|
||||
|
||||
```bash
|
||||
npx shadcn-ui@latest add alert
|
||||
```
|
||||
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="manual">
|
||||
|
||||
<Steps>
|
||||
|
||||
<Step>Copy and paste the following code into your project.</Step>
|
||||
|
||||
<ComponentSource name="alert" />
|
||||
|
||||
<Step>Update the import paths to match your project setup.</Step>
|
||||
|
||||
</Steps>
|
||||
|
||||
</TabsContent>
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
@@ -63,19 +35,3 @@ import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Default
|
||||
|
||||
<ComponentPreview
|
||||
name="alert-demo"
|
||||
description="An alert with an icon, title and description. The title says 'Heads up!' and the description is 'You can add components to your app using the cli.'."
|
||||
/>
|
||||
|
||||
### Destructive
|
||||
|
||||
<ComponentPreview
|
||||
name="alert-destructive"
|
||||
description="An alert with a destructive variant. The title says 'Delete this item?' and the description is 'This action cannot be undone.'."
|
||||
/>
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
title: Atom components
|
||||
description:
|
||||
---
|
||||
@@ -1,842 +0,0 @@
|
||||
---
|
||||
title: Data Table
|
||||
description: Powerful table and datagrids built using TanStack Table.
|
||||
component: true
|
||||
links:
|
||||
doc: https://tanstack.com/table/v8/docs/guide/introduction
|
||||
---
|
||||
|
||||
<ComponentPreview name="data-table-demo" peekCode wide />
|
||||
|
||||
## Introduction
|
||||
|
||||
Every data table or datagrid I've created has been unique. They all behave differently, have specific sorting and filtering requirements, and work with different data sources.
|
||||
|
||||
It doesn't make sense to combine all of these variations into a single component. If we do that, we'll lose the flexibility that [headless UI](https://tanstack.com/table/v8/docs/guide/introduction#what-is-headless-ui) provides.
|
||||
|
||||
So instead of a data-table component, I thought it would be more helpful to provide a guide on how to build your own.
|
||||
|
||||
We'll start with the basic `<Table />` component and build a complex data table from scratch.
|
||||
|
||||
<Callout className="mt-4">
|
||||
|
||||
**Tip:** If you find yourself using the same table in multiple places in your app, you can always extract it into a reusable component.
|
||||
|
||||
</Callout>
|
||||
|
||||
## Table of Contents
|
||||
|
||||
This guide will show you how to use [TanStack Table](https://tanstack.com/table) and the `<Table />` component to build your own custom data table. We'll cover the following topics:
|
||||
|
||||
- [Basic Table](#basic-table)
|
||||
- [Row Actions](#row-actions)
|
||||
- [Pagination](#pagination)
|
||||
- [Sorting](#sorting)
|
||||
- [Filtering](#filtering)
|
||||
- [Visibility](#visibility)
|
||||
- [Row Selection](#row-selection)
|
||||
- [Reusable Components](#reusable-components)
|
||||
|
||||
## Installation
|
||||
|
||||
1. Add the `<Table />` component to your project:
|
||||
|
||||
```bash
|
||||
npx shadcn-ui@latest add table
|
||||
```
|
||||
|
||||
2. Add `tanstack/react-table` dependency:
|
||||
|
||||
```bash
|
||||
npm install @tanstack/react-table
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
We are going to build a table to show recent payments. Here's what our data looks like:
|
||||
|
||||
```tsx showLineNumbers
|
||||
type Payment = {
|
||||
id: string
|
||||
amount: number
|
||||
status: 'pending' | 'processing' | 'success' | 'failed'
|
||||
email: string
|
||||
}
|
||||
|
||||
export const payments: Payment[] = [
|
||||
{
|
||||
id: '728ed52f',
|
||||
amount: 100,
|
||||
status: 'pending',
|
||||
email: 'm@example.com',
|
||||
},
|
||||
{
|
||||
id: '489e1d42',
|
||||
amount: 125,
|
||||
status: 'processing',
|
||||
email: 'example@gmail.com',
|
||||
},
|
||||
// ...
|
||||
]
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
Start by creating the following file structure:
|
||||
|
||||
```txt
|
||||
app
|
||||
└── payments
|
||||
├── columns.tsx
|
||||
├── data-table.tsx
|
||||
└── page.tsx
|
||||
```
|
||||
|
||||
I'm using a Next.js example here but this works for any other React framework.
|
||||
|
||||
- `columns.tsx` (client component) will contain our column definitions.
|
||||
- `data-table.tsx` (client component) will contain our `<DataTable />` component.
|
||||
- `page.tsx` (server component) is where we'll fetch data and render our table.
|
||||
|
||||
## Basic Table
|
||||
|
||||
Let's start by building a basic table.
|
||||
|
||||
<Steps>
|
||||
|
||||
### Column Definitions
|
||||
|
||||
First, we'll define our columns.
|
||||
|
||||
```tsx showLineNumbers title="app/payments/columns.tsx" {3,14-27}
|
||||
'use client'
|
||||
|
||||
import { ColumnDef } from '@tanstack/react-table'
|
||||
|
||||
// This type is used to define the shape of our data.
|
||||
// You can use a Zod schema here if you want.
|
||||
export type Payment = {
|
||||
id: string
|
||||
amount: number
|
||||
status: 'pending' | 'processing' | 'success' | 'failed'
|
||||
email: string
|
||||
}
|
||||
|
||||
export const columns: ColumnDef<Payment>[] = [
|
||||
{
|
||||
accessorKey: 'status',
|
||||
header: 'Status',
|
||||
},
|
||||
{
|
||||
accessorKey: 'email',
|
||||
header: 'Email',
|
||||
},
|
||||
{
|
||||
accessorKey: 'amount',
|
||||
header: 'Amount',
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
<Callout className="mt-4">
|
||||
|
||||
**Note:** Columns are where you define the core of what your table
|
||||
will look like. They define the data that will be displayed, how it will be
|
||||
formatted, sorted and filtered.
|
||||
|
||||
</Callout>
|
||||
|
||||
### `<DataTable />` component
|
||||
|
||||
Next, we'll create a `<DataTable />` component to render our table.
|
||||
|
||||
```tsx showLineNumbers title="app/payments/data-table.tsx"
|
||||
'use client'
|
||||
|
||||
import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table'
|
||||
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table'
|
||||
|
||||
interface DataTableProps<TData, TValue> {
|
||||
columns: ColumnDef<TData, TValue>[]
|
||||
data: TData[]
|
||||
}
|
||||
|
||||
export function DataTable<TData, TValue>({ columns, data }: DataTableProps<TData, TValue>) {
|
||||
const table = useReactTable({
|
||||
data,
|
||||
columns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => {
|
||||
return (
|
||||
<TableHead key={header.id}>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(header.column.columnDef.header, header.getContext())}
|
||||
</TableHead>
|
||||
)
|
||||
})}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{table.getRowModel().rows?.length ? (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={columns.length} className="h-24 text-center">
|
||||
No results.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
<Callout>
|
||||
|
||||
**Tip**: If you find yourself using `<DataTable />` in multiple places, this is the component you could make reusable by extracting it to `components/ui/data-table.tsx`.
|
||||
|
||||
`<DataTable columns={columns} data={data} />`
|
||||
|
||||
</Callout>
|
||||
|
||||
### Render the table
|
||||
|
||||
Finally, we'll render our table in our page component.
|
||||
|
||||
```tsx showLineNumbers title="app/payments/page.tsx" {22}
|
||||
import { Payment, columns } from './columns'
|
||||
import { DataTable } from './data-table'
|
||||
|
||||
async function getData(): Promise<Payment[]> {
|
||||
// Fetch data from your API here.
|
||||
return [
|
||||
{
|
||||
id: '728ed52f',
|
||||
amount: 100,
|
||||
status: 'pending',
|
||||
email: 'm@example.com',
|
||||
},
|
||||
// ...
|
||||
]
|
||||
}
|
||||
|
||||
export default async function DemoPage() {
|
||||
const data = await getData()
|
||||
|
||||
return (
|
||||
<div className="container mx-auto py-10">
|
||||
<DataTable columns={columns} data={data} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</Steps>
|
||||
|
||||
## Cell Formatting
|
||||
|
||||
Let's format the amount cell to display the dollar amount. We'll also align the cell to the right.
|
||||
|
||||
<Steps>
|
||||
|
||||
### Update columns definition
|
||||
|
||||
Update the `header` and `cell` definitions for amount as follows:
|
||||
|
||||
```tsx showLineNumbers title="app/payments/columns.tsx" {4-15}
|
||||
export const columns: ColumnDef<Payment>[] = [
|
||||
{
|
||||
accessorKey: 'amount',
|
||||
header: () => <div className="text-right">Amount</div>,
|
||||
cell: ({ row }) => {
|
||||
const amount = parseFloat(row.getValue('amount'))
|
||||
const formatted = new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
}).format(amount)
|
||||
|
||||
return <div className="text-right font-medium">{formatted}</div>
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
You can use the same approach to format other cells and headers.
|
||||
|
||||
</Steps>
|
||||
|
||||
## Row Actions
|
||||
|
||||
Let's add row actions to our table. We'll use a `<Dropdown />` component for this.
|
||||
|
||||
<Steps>
|
||||
|
||||
### Update columns definition
|
||||
|
||||
Update our columns definition to add a new `actions` column. The `actions` cell returns a `<Dropdown />` component.
|
||||
|
||||
```tsx showLineNumbers title="app/payments/columns.tsx" {4,6-14,18-45}
|
||||
'use client'
|
||||
|
||||
import { ColumnDef } from '@tanstack/react-table'
|
||||
import { MoreHorizontal } from 'lucide-react'
|
||||
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
|
||||
export const columns: ColumnDef<Payment>[] = [
|
||||
// ...
|
||||
{
|
||||
id: 'actions',
|
||||
cell: ({ row }) => {
|
||||
const payment = row.original
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" className="h-8 w-8 p-0">
|
||||
<span className="sr-only">Open menu</span>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||
<DropdownMenuItem onClick={() => navigator.clipboard.writeText(payment.id)}>
|
||||
Copy payment ID
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>View customer</DropdownMenuItem>
|
||||
<DropdownMenuItem>View payment details</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
},
|
||||
},
|
||||
// ...
|
||||
]
|
||||
```
|
||||
|
||||
You can access the row data using `row.original` in the `cell` function. Use this to handle actions for your row eg. use the `id` to make a DELETE call to your API.
|
||||
|
||||
</Steps>
|
||||
|
||||
## Pagination
|
||||
|
||||
Next, we'll add pagination to our table.
|
||||
|
||||
<Steps>
|
||||
|
||||
### Update `<DataTable>`
|
||||
|
||||
```tsx showLineNumbers title="app/payments/data-table.tsx" {5,17}
|
||||
import {
|
||||
ColumnDef,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getPaginationRowModel,
|
||||
useReactTable,
|
||||
} from '@tanstack/react-table'
|
||||
|
||||
export function DataTable<TData, TValue>({ columns, data }: DataTableProps<TData, TValue>) {
|
||||
const table = useReactTable({
|
||||
data,
|
||||
columns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
})
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
This will automatically paginate your rows into pages of 10. See the [pagination docs](https://tanstack.com/table/v8/docs/api/features/pagination) for more information on customizing page size and implementing manual pagination.
|
||||
|
||||
### Add pagination controls
|
||||
|
||||
We can add pagination controls to our table using the `<Button />` component and the `table.previousPage()`, `table.nextPage()` API methods.
|
||||
|
||||
```tsx showLineNumbers title="app/payments/data-table.tsx" {1,15,21-39}
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export function DataTable<TData, TValue>({
|
||||
columns,
|
||||
data,
|
||||
}: DataTableProps<TData, TValue>) {
|
||||
const table = useReactTable({
|
||||
data,
|
||||
columns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="rounded-md border">
|
||||
<Table>
|
||||
{ // .... }
|
||||
</Table>
|
||||
</div>
|
||||
<div className="flex items-center justify-end space-x-2 py-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => table.previousPage()}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
>
|
||||
Previous
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => table.nextPage()}
|
||||
disabled={!table.getCanNextPage()}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
See [Reusable Components](#reusable-components) section for a more advanced pagination component.
|
||||
|
||||
</Steps>
|
||||
|
||||
## Sorting
|
||||
|
||||
Let's make the email column sortable.
|
||||
|
||||
<Steps>
|
||||
|
||||
### Update `<DataTable>`
|
||||
|
||||
```tsx showLineNumbers title="app/payments/data-table.tsx" showLineNumbers {3,6,10,18,25-28}
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import {
|
||||
ColumnDef,
|
||||
SortingState,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getPaginationRowModel,
|
||||
getSortedRowModel,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table"
|
||||
|
||||
export function DataTable<TData, TValue>({
|
||||
columns,
|
||||
data,
|
||||
}: DataTableProps<TData, TValue>) {
|
||||
const [sorting, setSorting] = React.useState<SortingState>([])
|
||||
|
||||
const table = useReactTable({
|
||||
data,
|
||||
columns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
onSortingChange: setSorting,
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
state: {
|
||||
sorting,
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="rounded-md border">
|
||||
<Table>{ ... }</Table>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Make header cell sortable
|
||||
|
||||
We can now update the `email` header cell to add sorting controls.
|
||||
|
||||
```tsx showLineNumbers title="app/payments/columns.tsx" {4,9-19}
|
||||
'use client'
|
||||
|
||||
import { ColumnDef } from '@tanstack/react-table'
|
||||
import { ArrowUpDown, MoreHorizontal } from 'lucide-react'
|
||||
|
||||
export const columns: ColumnDef<Payment>[] = [
|
||||
{
|
||||
accessorKey: 'email',
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||
>
|
||||
Email
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
)
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
This will automatically sort the table (asc and desc) when the user toggles on the header cell.
|
||||
|
||||
</Steps>
|
||||
|
||||
## Filtering
|
||||
|
||||
Let's add a search input to filter emails in our table.
|
||||
|
||||
<Steps>
|
||||
|
||||
### Update `<DataTable>`
|
||||
|
||||
```tsx showLineNumbers title="app/payments/data-table.tsx" {6,10,17,24-26,35-36,39,45-54}
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import {
|
||||
ColumnDef,
|
||||
ColumnFiltersState,
|
||||
SortingState,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getFilteredRowModel,
|
||||
getPaginationRowModel,
|
||||
getSortedRowModel,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
|
||||
export function DataTable<TData, TValue>({
|
||||
columns,
|
||||
data,
|
||||
}: DataTableProps<TData, TValue>) {
|
||||
const [sorting, setSorting] = React.useState<SortingState>([])
|
||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||
[]
|
||||
)
|
||||
|
||||
const table = useReactTable({
|
||||
data,
|
||||
columns,
|
||||
onSortingChange: setSorting,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
onColumnFiltersChange: setColumnFilters,
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
state: {
|
||||
sorting,
|
||||
columnFilters,
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center py-4">
|
||||
<Input
|
||||
placeholder="Filter emails..."
|
||||
value={(table.getColumn("email")?.getFilterValue() as string) ?? ""}
|
||||
onChange={(event) =>
|
||||
table.getColumn("email")?.setFilterValue(event.target.value)
|
||||
}
|
||||
className="max-w-sm"
|
||||
/>
|
||||
</div>
|
||||
<div className="rounded-md border">
|
||||
<Table>{ ... }</Table>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Filtering is now enabled for the `email` column. You can add filters to other columns as well. See the [filtering docs](https://tanstack.com/table/v8/docs/guide/filters) for more information on customizing filters.
|
||||
|
||||
</Steps>
|
||||
|
||||
## Visibility
|
||||
|
||||
Adding column visibility is fairly simple using `@tanstack/react-table` visibility API.
|
||||
|
||||
<Steps>
|
||||
|
||||
### Update `<DataTable>`
|
||||
|
||||
```tsx showLineNumbers title="app/payments/data-table.tsx" {8,18-23,33-34,45,49,64-91}
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import {
|
||||
ColumnDef,
|
||||
ColumnFiltersState,
|
||||
SortingState,
|
||||
VisibilityState,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getFilteredRowModel,
|
||||
getPaginationRowModel,
|
||||
getSortedRowModel,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
|
||||
export function DataTable<TData, TValue>({
|
||||
columns,
|
||||
data,
|
||||
}: DataTableProps<TData, TValue>) {
|
||||
const [sorting, setSorting] = React.useState<SortingState>([])
|
||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||
[]
|
||||
)
|
||||
const [columnVisibility, setColumnVisibility] =
|
||||
React.useState<VisibilityState>({})
|
||||
|
||||
const table = useReactTable({
|
||||
data,
|
||||
columns,
|
||||
onSortingChange: setSorting,
|
||||
onColumnFiltersChange: setColumnFilters,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
onColumnVisibilityChange: setColumnVisibility,
|
||||
state: {
|
||||
sorting,
|
||||
columnFilters,
|
||||
columnVisibility,
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center py-4">
|
||||
<Input
|
||||
placeholder="Filter emails..."
|
||||
value={table.getColumn("email")?.getFilterValue() as string}
|
||||
onChange={(event) =>
|
||||
table.getColumn("email")?.setFilterValue(event.target.value)
|
||||
}
|
||||
className="max-w-sm"
|
||||
/>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" className="ml-auto">
|
||||
Columns
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
{table
|
||||
.getAllColumns()
|
||||
.filter(
|
||||
(column) => column.getCanHide()
|
||||
)
|
||||
.map((column) => {
|
||||
return (
|
||||
<DropdownMenuCheckboxItem
|
||||
key={column.id}
|
||||
className="capitalize"
|
||||
checked={column.getIsVisible()}
|
||||
onCheckedChange={(value) =>
|
||||
column.toggleVisibility(!!value)
|
||||
}
|
||||
>
|
||||
{column.id}
|
||||
</DropdownMenuCheckboxItem>
|
||||
)
|
||||
})}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<div className="rounded-md border">
|
||||
<Table>{ ... }</Table>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
This adds a dropdown menu that you can use to toggle column visibility.
|
||||
|
||||
</Steps>
|
||||
|
||||
## Row Selection
|
||||
|
||||
Next, we're going to add row selection to our table.
|
||||
|
||||
<Steps>
|
||||
|
||||
### Update column definitions
|
||||
|
||||
```tsx showLineNumbers title="app/payments/columns.tsx" {6,9-27}
|
||||
'use client'
|
||||
|
||||
import { ColumnDef } from '@tanstack/react-table'
|
||||
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Checkbox } from '@/components/ui/checkbox'
|
||||
|
||||
export const columns: ColumnDef<Payment>[] = [
|
||||
{
|
||||
id: 'select',
|
||||
header: ({ table }) => (
|
||||
<Checkbox
|
||||
checked={
|
||||
table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && 'indeterminate')
|
||||
}
|
||||
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
||||
aria-label="Select all"
|
||||
/>
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<Checkbox
|
||||
checked={row.getIsSelected()}
|
||||
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||
aria-label="Select row"
|
||||
/>
|
||||
),
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
### Update `<DataTable>`
|
||||
|
||||
```tsx showLineNumbers title="app/payments/data-table.tsx" {11,23,28}
|
||||
export function DataTable<TData, TValue>({ columns, data }: DataTableProps<TData, TValue>) {
|
||||
const [sorting, setSorting] = React.useState<SortingState>([])
|
||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([])
|
||||
const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({})
|
||||
const [rowSelection, setRowSelection] = React.useState({})
|
||||
|
||||
const table = useReactTable({
|
||||
data,
|
||||
columns,
|
||||
onSortingChange: setSorting,
|
||||
onColumnFiltersChange: setColumnFilters,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
onColumnVisibilityChange: setColumnVisibility,
|
||||
onRowSelectionChange: setRowSelection,
|
||||
state: {
|
||||
sorting,
|
||||
columnFilters,
|
||||
columnVisibility,
|
||||
rowSelection,
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="rounded-md border">
|
||||
<Table />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
This adds a checkbox to each row and a checkbox in the header to select all rows.
|
||||
|
||||
### Show selected rows
|
||||
|
||||
You can show the number of selected rows using the `table.getFilteredSelectedRowModel()` API.
|
||||
|
||||
```tsx
|
||||
<div className="flex-1 text-sm text-muted-foreground">
|
||||
{table.getFilteredSelectedRowModel().rows.length} of {table.getFilteredRowModel().rows.length}{' '}
|
||||
row(s) selected.
|
||||
</div>
|
||||
```
|
||||
|
||||
</Steps>
|
||||
|
||||
## Reusable Components
|
||||
|
||||
Here are some components you can use to build your data tables. This is from the [Tasks](/examples/tasks) demo.
|
||||
|
||||
### Column header
|
||||
|
||||
Make any column header sortable and hideable.
|
||||
|
||||
<ComponentSource src="/app/(app)/examples/tasks/components/data-table-column-header.tsx" />
|
||||
|
||||
```tsx {5}
|
||||
export const columns = [
|
||||
{
|
||||
accessorKey: 'email',
|
||||
header: ({ column }) => <DataTableColumnHeader column={column} title="Email" />,
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
### Pagination
|
||||
|
||||
Add pagination controls to your table including page size and selection count.
|
||||
|
||||
<ComponentSource src="/app/(app)/examples/tasks/components/data-table-pagination.tsx" />
|
||||
|
||||
```tsx
|
||||
<DataTablePagination table={table} />
|
||||
```
|
||||
|
||||
### Column toggle
|
||||
|
||||
A component to toggle column visibility.
|
||||
|
||||
<ComponentSource src="/app/(app)/examples/tasks/components/data-table-view-options.tsx" />
|
||||
|
||||
```tsx
|
||||
<DataTableViewOptions table={table} />
|
||||
```
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
title: Dialog
|
||||
description: A window overlaid on either the primary window or another dialog window, rendering the content underneath inert.
|
||||
featured: true
|
||||
description: A general-purpose modal for non-critical flows, forms, and custom interactions.
|
||||
component: true
|
||||
links:
|
||||
doc: https://www.radix-ui.com/docs/primitives/components/dialog
|
||||
@@ -11,6 +10,20 @@ source:
|
||||
shadcn: true
|
||||
---
|
||||
|
||||
Dialog is a flexible, general-purpose modal used for bespoke interactions such as forms, pickers, multi-step flows, or displaying non-urgent information. Unlike confirmation-focused dialogs, it is designed to be safely dismissible and does not force an explicit decision.
|
||||
|
||||
Dialog can be closed by clicking outside the modal or pressing the Escape key, making it suitable for workflows where cancellation is expected and low-risk.
|
||||
|
||||
Use Dialog when you need full control over layout, content, and behavior, and the interaction does not involve a critical or destructive action.
|
||||
|
||||
For confirmations or warnings, try to use an existing component:
|
||||
|
||||
- Use [Alert Dialog](../components/alert-dialog) for critical confirmations that require an explicit decision
|
||||
- Use [Confirmation Modal](../fragments/confirmation-modal) when additional context is needed for a confirmation
|
||||
- Use [Text Confirm Dialog](../fragments/text-confirm-dialog) for highly destructive actions that require typed intent
|
||||
|
||||
See [Modality](../ui-patterns/modality) for guidance on choosing the appropriate dialog pattern.
|
||||
|
||||
<ComponentPreview name="dialog-demo" peekCode wide />
|
||||
|
||||
## Usage
|
||||
@@ -31,23 +44,31 @@ import {
|
||||
<DialogTrigger>Open</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Are you absolutely sure?</DialogTitle>
|
||||
<DialogTitle>Project settings</DialogTitle>
|
||||
<DialogDescription>
|
||||
This action cannot be undone. This will permanently delete your account and remove your data
|
||||
from our servers.
|
||||
Update configuration options for this project. Changes can be discarded at any time.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
{/* Custom content goes here */}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
```
|
||||
|
||||
## Guidelines
|
||||
|
||||
- **Use for non-critical interactions:** Dialog is appropriate when dismissal has no serious consequences.
|
||||
- **Design for cancellation**: Assume users may close the dialog without completing the action.
|
||||
- **Keep focus contained**: Dialog content should remain scoped to a single task or flow.
|
||||
- **Avoid destructive confirmations**: If the dialog’s primary purpose is to confirm a risky action, use a confirmation-focused pattern instead.
|
||||
- **Compose freely**: Dialog is intentionally unopinionated. Build custom layouts, forms, or step-based flows as needed.
|
||||
|
||||
## Examples
|
||||
|
||||
### Custom close button
|
||||
|
||||
<ComponentPreview name="dialog-close-button" />
|
||||
|
||||
### Centered behaviour
|
||||
### Centered behavior
|
||||
|
||||
You can control whether the dialog is centered by passing `centered={false}` to the `DialogContent` component.
|
||||
|
||||
@@ -69,30 +90,27 @@ You can control whether the dialog is centered by passing `centered={false}` to
|
||||
To activate the `Dialog` component from within a `Context Menu` or `Dropdown Menu`, you must encase the `Context Menu` or
|
||||
`Dropdown Menu` component in the `Dialog` component. For more information, refer to the linked issue [here](https://github.com/radix-ui/primitives/issues/1836).
|
||||
|
||||
```tsx {14-25}
|
||||
```tsx {7-11, 14-23}
|
||||
<Dialog>
|
||||
<ContextMenu>
|
||||
<ContextMenuTrigger>Dialog not centered</ContextMenuTrigger>
|
||||
<ContextMenuTrigger>Show Menu</ContextMenuTrigger>
|
||||
<ContextMenuContent>
|
||||
<ContextMenuItem>Open</ContextMenuItem>
|
||||
<ContextMenuItem>Download</ContextMenuItem>
|
||||
<DialogTrigger asChild>
|
||||
<ContextMenuItem>
|
||||
<span>Delete</span>
|
||||
<span>Show Dialog</span>
|
||||
</ContextMenuItem>
|
||||
</DialogTrigger>
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Are you absolutely sure?</DialogTitle>
|
||||
<DialogDescription>
|
||||
This action cannot be undone. Are you sure you want to permanently delete this file from our
|
||||
servers?
|
||||
</DialogDescription>
|
||||
<DialogTitle>Edit profile</DialogTitle>
|
||||
<DialogDescription>Make changes to your profile here.</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button type="submit">Confirm</Button>
|
||||
<Button>Save changes</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@@ -15,12 +15,6 @@ source:
|
||||
|
||||
## Installation
|
||||
|
||||
So I guess I can write anything i want in here.
|
||||
|
||||
## Props
|
||||
|
||||
{/* <ComponentProps component={'/Dropdown'} foo="world" /> */}
|
||||
|
||||
<Tabs defaultValue="cli">
|
||||
|
||||
<TabsList>
|
||||
|
||||
@@ -0,0 +1,333 @@
|
||||
---
|
||||
title: Field
|
||||
description: Combine labels, controls, and help text to compose accessible form fields and grouped inputs.
|
||||
component: true
|
||||
source:
|
||||
shadcn: true
|
||||
---
|
||||
|
||||
<ComponentPreview
|
||||
name="field-demo"
|
||||
className="[&_.preview]:h-[800px] [&_.preview]:p-6 md:[&_.preview]:h-[850px]"
|
||||
/>
|
||||
|
||||
## Installation
|
||||
|
||||
<Tabs defaultValue="cli">
|
||||
|
||||
<TabsList>
|
||||
<TabsTrigger value="cli">CLI</TabsTrigger>
|
||||
<TabsTrigger value="manual">Manual</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="cli">
|
||||
|
||||
```bash
|
||||
npx shadcn@latest add field
|
||||
```
|
||||
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="manual">
|
||||
|
||||
<Steps>
|
||||
|
||||
<Step>Copy and paste the following code into your project.</Step>
|
||||
|
||||
<ComponentSource name="field" title="components/ui/field.tsx" />
|
||||
|
||||
<Step>Update the import paths to match your project setup.</Step>
|
||||
|
||||
</Steps>
|
||||
|
||||
</TabsContent>
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx showLineNumbers
|
||||
import {
|
||||
Field,
|
||||
FieldContent,
|
||||
FieldDescription,
|
||||
FieldError,
|
||||
FieldGroup,
|
||||
FieldLabel,
|
||||
FieldLegend,
|
||||
FieldSeparator,
|
||||
FieldSet,
|
||||
FieldTitle,
|
||||
} from '@/components/ui/field'
|
||||
```
|
||||
|
||||
```tsx showLineNumbers
|
||||
<FieldSet>
|
||||
<FieldLegend>Profile</FieldLegend>
|
||||
<FieldDescription>This appears on invoices and emails.</FieldDescription>
|
||||
<FieldGroup>
|
||||
<Field>
|
||||
<FieldLabel htmlFor="name">Full name</FieldLabel>
|
||||
<Input id="name" autoComplete="off" placeholder="Evil Rabbit" />
|
||||
<FieldDescription>This appears on invoices and emails.</FieldDescription>
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel htmlFor="username">Username</FieldLabel>
|
||||
<Input id="username" autoComplete="off" aria-invalid />
|
||||
<FieldError>Choose another username.</FieldError>
|
||||
</Field>
|
||||
<Field orientation="horizontal">
|
||||
<Switch id="newsletter" />
|
||||
<FieldLabel htmlFor="newsletter">Subscribe to the newsletter</FieldLabel>
|
||||
</Field>
|
||||
</FieldGroup>
|
||||
</FieldSet>
|
||||
```
|
||||
|
||||
## Anatomy
|
||||
|
||||
The `Field` family is designed for composing accessible forms. A typical field is structured as follows:
|
||||
|
||||
```tsx showLineNumbers
|
||||
<Field>
|
||||
<FieldLabel htmlFor="input-id">Label</FieldLabel>
|
||||
{/* Input, Select, Switch, etc. */}
|
||||
<FieldDescription>Optional helper text.</FieldDescription>
|
||||
<FieldError>Validation message.</FieldError>
|
||||
</Field>
|
||||
```
|
||||
|
||||
- `Field` is the core wrapper for a single field.
|
||||
- `FieldContent` is a flex column that groups label and description. Not required if you have no description.
|
||||
- Wrap related fields with `FieldGroup`, and use `FieldSet` with `FieldLegend` for semantic grouping.
|
||||
|
||||
## Form
|
||||
|
||||
See the [Form](/docs/forms) documentation for building forms with the `Field` component and [React Hook Form](/docs/forms/react-hook-form) or [Tanstack Form](/docs/forms/tanstack-form).
|
||||
|
||||
## Examples
|
||||
|
||||
### Input
|
||||
|
||||
<ComponentPreview name="field-input" className="!mb-4 [&_.preview]:p-6" />
|
||||
|
||||
### Textarea
|
||||
|
||||
<ComponentPreview name="field-textarea" className="!mb-4 [&_.preview]:p-6" />
|
||||
|
||||
### Select
|
||||
|
||||
<ComponentPreview name="field-select" className="!mb-4 [&_.preview]:p-6" />
|
||||
|
||||
### Slider
|
||||
|
||||
<ComponentPreview name="field-slider" className="!mb-4 [&_.preview]:p-6" />
|
||||
|
||||
### Fieldset
|
||||
|
||||
<ComponentPreview name="field-fieldset" className="!mb-4 [&_.preview]:p-6" />
|
||||
|
||||
### Checkbox
|
||||
|
||||
<ComponentPreview name="field-checkbox" className="!mb-4 [&_.preview]:p-6" />
|
||||
|
||||
### Radio
|
||||
|
||||
<ComponentPreview name="field-radio" className="!mb-4 [&_.preview]:p-6" />
|
||||
|
||||
### Switch
|
||||
|
||||
<ComponentPreview name="field-switch" className="!mb-4 [&_.preview]:p-6" />
|
||||
|
||||
### Choice Card
|
||||
|
||||
Wrap `Field` components inside `FieldLabel` to create selectable field groups. This works with `RadioItem`, `Checkbox` and `Switch` components.
|
||||
|
||||
<ComponentPreview name="field-choice-card" className="!mb-4 [&_.preview]:p-6" />
|
||||
|
||||
### Field Group
|
||||
|
||||
Stack `Field` components with `FieldGroup`. Add `FieldSeparator` to divide them.
|
||||
|
||||
<ComponentPreview name="field-group" className="!mb-4 [&_.preview]:p-6" />
|
||||
|
||||
## Responsive Layout
|
||||
|
||||
- **Vertical fields:** Default orientation stacks label, control, and helper text—ideal for mobile-first layouts.
|
||||
- **Horizontal fields:** Set `orientation="horizontal"` on `Field` to align the label and control side-by-side. Pair with `FieldContent` to keep descriptions aligned.
|
||||
- **Responsive fields:** Set `orientation="responsive"` for automatic column layouts inside container-aware parents. Apply `@container/field-group` classes on `FieldGroup` to switch orientations at specific breakpoints.
|
||||
|
||||
<ComponentPreview
|
||||
name="field-responsive"
|
||||
className="!mb-4 [&_.preview]:h-[650px] [&_.preview]:p-6 [&_.preview]:md:h-[500px] [&_.preview]:md:p-10"
|
||||
/>
|
||||
|
||||
## Validation and Errors
|
||||
|
||||
- Add `data-invalid` to `Field` to switch the entire block into an error state.
|
||||
- Add `aria-invalid` on the input itself for assistive technologies.
|
||||
- Render `FieldError` immediately after the control or inside `FieldContent` to keep error messages aligned with the field.
|
||||
|
||||
```tsx showLineNumbers /data-invalid/ /aria-invalid/
|
||||
<Field data-invalid>
|
||||
<FieldLabel htmlFor="email">Email</FieldLabel>
|
||||
<Input id="email" type="email" aria-invalid />
|
||||
<FieldError>Enter a valid email address.</FieldError>
|
||||
</Field>
|
||||
```
|
||||
|
||||
## Accessibility
|
||||
|
||||
- `FieldSet` and `FieldLegend` keep related controls grouped for keyboard and assistive tech users.
|
||||
- `Field` outputs `role="group"` so nested controls inherit labeling from `FieldLabel` and `FieldLegend` when combined.
|
||||
- Apply `FieldSeparator` sparingly to ensure screen readers encounter clear section boundaries.
|
||||
|
||||
## API Reference
|
||||
|
||||
### FieldSet
|
||||
|
||||
Container that renders a semantic `fieldset` with spacing presets.
|
||||
|
||||
| Prop | Type | Default |
|
||||
| ----------- | -------- | ------- |
|
||||
| `className` | `string` | |
|
||||
|
||||
```tsx
|
||||
<FieldSet>
|
||||
<FieldLegend>Delivery</FieldLegend>
|
||||
<FieldGroup>{/* Fields */}</FieldGroup>
|
||||
</FieldSet>
|
||||
```
|
||||
|
||||
### FieldLegend
|
||||
|
||||
Legend element for a `FieldSet`. Switch to the `label` variant to align with label sizing.
|
||||
|
||||
| Prop | Type | Default |
|
||||
| ----------- | --------------------- | ---------- |
|
||||
| `variant` | `"legend" \| "label"` | `"legend"` |
|
||||
| `className` | `string` | |
|
||||
|
||||
```tsx
|
||||
<FieldLegend variant="label">Notification Preferences</FieldLegend>
|
||||
```
|
||||
|
||||
The `FieldLegend` has two variants: `legend` and `label`. The `label` variant applies label sizing and alignment. Handy if you have nested `FieldSet`.
|
||||
|
||||
### FieldGroup
|
||||
|
||||
Layout wrapper that stacks `Field` components and enables container queries for responsive orientations.
|
||||
|
||||
| Prop | Type | Default |
|
||||
| ----------- | -------- | ------- |
|
||||
| `className` | `string` | |
|
||||
|
||||
```tsx
|
||||
<FieldGroup className="@container/field-group flex flex-col gap-6">
|
||||
<Field>{/* ... */}</Field>
|
||||
<Field>{/* ... */}</Field>
|
||||
</FieldGroup>
|
||||
```
|
||||
|
||||
### Field
|
||||
|
||||
The core wrapper for a single field. Provides orientation control, invalid state styling, and spacing.
|
||||
|
||||
| Prop | Type | Default |
|
||||
| -------------- | -------------------------------------------- | ------------ |
|
||||
| `orientation` | `"vertical" \| "horizontal" \| "responsive"` | `"vertical"` |
|
||||
| `className` | `string` | |
|
||||
| `data-invalid` | `boolean` | |
|
||||
|
||||
```tsx
|
||||
<Field orientation="horizontal">
|
||||
<FieldLabel htmlFor="remember">Remember me</FieldLabel>
|
||||
<Switch id="remember" />
|
||||
</Field>
|
||||
```
|
||||
|
||||
### FieldContent
|
||||
|
||||
Flex column that groups control and descriptions when the label sits beside the control. Not required if you have no description.
|
||||
|
||||
| Prop | Type | Default |
|
||||
| ----------- | -------- | ------- |
|
||||
| `className` | `string` | |
|
||||
|
||||
```tsx
|
||||
<Field>
|
||||
<Checkbox id="notifications" />
|
||||
<FieldContent>
|
||||
<FieldLabel htmlFor="notifications">Notifications</FieldLabel>
|
||||
<FieldDescription>Email, SMS, and push options.</FieldDescription>
|
||||
</FieldContent>
|
||||
</Field>
|
||||
```
|
||||
|
||||
### FieldLabel
|
||||
|
||||
Label styled for both direct inputs and nested `Field` children.
|
||||
|
||||
| Prop | Type | Default |
|
||||
| ----------- | --------- | ------- |
|
||||
| `className` | `string` | |
|
||||
| `asChild` | `boolean` | `false` |
|
||||
|
||||
```tsx
|
||||
<FieldLabel htmlFor="email">Email</FieldLabel>
|
||||
```
|
||||
|
||||
### FieldTitle
|
||||
|
||||
Renders a title with label styling inside `FieldContent`.
|
||||
|
||||
| Prop | Type | Default |
|
||||
| ----------- | -------- | ------- |
|
||||
| `className` | `string` | |
|
||||
|
||||
```tsx
|
||||
<FieldContent>
|
||||
<FieldTitle>Enable Touch ID</FieldTitle>
|
||||
<FieldDescription>Unlock your device faster.</FieldDescription>
|
||||
</FieldContent>
|
||||
```
|
||||
|
||||
### FieldDescription
|
||||
|
||||
Helper text slot that automatically balances long lines in horizontal layouts.
|
||||
|
||||
| Prop | Type | Default |
|
||||
| ----------- | -------- | ------- |
|
||||
| `className` | `string` | |
|
||||
|
||||
```tsx
|
||||
<FieldDescription>We never share your email with anyone.</FieldDescription>
|
||||
```
|
||||
|
||||
### FieldSeparator
|
||||
|
||||
Visual divider to separate sections inside a `FieldGroup`. Accepts optional inline content.
|
||||
|
||||
| Prop | Type | Default |
|
||||
| ----------- | -------- | ------- |
|
||||
| `className` | `string` | |
|
||||
|
||||
```tsx
|
||||
<FieldSeparator>Or continue with</FieldSeparator>
|
||||
```
|
||||
|
||||
### FieldError
|
||||
|
||||
Accessible error container that accepts children or an `errors` array (e.g., from `react-hook-form`).
|
||||
|
||||
| Prop | Type | Default |
|
||||
| ----------- | ------------------------------------------ | ------- |
|
||||
| `errors` | `Array<{ message?: string } \| undefined>` | |
|
||||
| `className` | `string` | |
|
||||
|
||||
```tsx
|
||||
<FieldError errors={errors.username} />
|
||||
```
|
||||
|
||||
When the `errors` array contains multiple messages, the component renders a list automatically.
|
||||
|
||||
`FieldError` also accepts issues produced by any validator that implements [Standard Schema](https://standardschema.dev/), including Zod, Valibot, and ArkType. Pass the `issues` array from the schema result directly to render a unified error list across libraries.
|
||||
Loaded 100 of 1310 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user