mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-30 16:14:13 +00:00
Compare commits
1 Commits
shadcn@2.4
...
shadcn/cli
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8392c7fa0c |
@@ -7,5 +7,5 @@
|
|||||||
"access": "public",
|
"access": "public",
|
||||||
"baseBranch": "main",
|
"baseBranch": "main",
|
||||||
"updateInternalDependencies": "patch",
|
"updateInternalDependencies": "patch",
|
||||||
"ignore": ["www", "v4"]
|
"ignore": ["www", "**-template"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
node_modules/
|
|
||||||
target/
|
|
||||||
.next/
|
|
||||||
build/
|
|
||||||
dist/
|
|
||||||
|
|
||||||
/templates/
|
|
||||||
/fixtures/
|
|
||||||
@@ -8,7 +8,6 @@
|
|||||||
"plugin:tailwindcss/recommended"
|
"plugin:tailwindcss/recommended"
|
||||||
],
|
],
|
||||||
"plugins": ["tailwindcss"],
|
"plugins": ["tailwindcss"],
|
||||||
"ignorePatterns": ["**/fixtures/**"],
|
|
||||||
"rules": {
|
"rules": {
|
||||||
"@next/next/no-html-link-for-pages": "off",
|
"@next/next/no-html-link-for-pages": "off",
|
||||||
"tailwindcss/no-custom-classname": "off",
|
"tailwindcss/no-custom-classname": "off",
|
||||||
|
|||||||
25
.github/DISCUSSION_TEMPLATE/blocks-request.yml
vendored
25
.github/DISCUSSION_TEMPLATE/blocks-request.yml
vendored
@@ -1,25 +0,0 @@
|
|||||||
title: "[blocks]: "
|
|
||||||
labels: ["Blocks Request"]
|
|
||||||
body:
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
### Thanks for taking the time to create a block request! Please search open/closed requests before submitting, as the block or a similar one may have already been requested.
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: block-description
|
|
||||||
attributes:
|
|
||||||
label: Description
|
|
||||||
description: Tell us about your block request
|
|
||||||
placeholder: "A dashboard for an e-commerce website showing sales, orders, and customers..."
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: input
|
|
||||||
id: block-example-url
|
|
||||||
attributes:
|
|
||||||
label: Example
|
|
||||||
description: Link to an example of the block
|
|
||||||
placeholder: ex. https://example.com
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
3
.github/FUNDING.yml
vendored
3
.github/FUNDING.yml
vendored
@@ -1,3 +0,0 @@
|
|||||||
# These are supported funding model platforms
|
|
||||||
|
|
||||||
github: [shadcn]
|
|
||||||
85
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
85
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,85 +0,0 @@
|
|||||||
name: "Bug report"
|
|
||||||
description: Report an issue
|
|
||||||
title: '[bug]: '
|
|
||||||
labels: ["bug"]
|
|
||||||
body:
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
### Thanks for taking the time to create a bug report. Please search open/closed issues before submitting, as the issue may have already been reported/addressed.
|
|
||||||
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
#### If you aren't sure this is a bug or not, please open a discussion instead:
|
|
||||||
- [Discussions](https://github.com/shadcn-ui/ui/discussions/new?category=general)
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: bug-description
|
|
||||||
attributes:
|
|
||||||
label: Describe the bug
|
|
||||||
description: A clear and concise description of what the bug is. If you intend to submit a PR for this issue, tell us how in the description. Thanks!
|
|
||||||
placeholder: Bug description
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: input
|
|
||||||
id: components-affected
|
|
||||||
attributes:
|
|
||||||
label: Affected component/components
|
|
||||||
description: Which shadcn/ui components are affected?
|
|
||||||
placeholder: ex. Button, Checkbox...
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: reproduction
|
|
||||||
attributes:
|
|
||||||
label: How to reproduce
|
|
||||||
description: A step-by-step description of how to reproduce the bug.
|
|
||||||
placeholder: |
|
|
||||||
1. Go to '...'
|
|
||||||
2. Click on '....'
|
|
||||||
3. See error
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: input
|
|
||||||
id: codesandbox-stackblitz
|
|
||||||
attributes:
|
|
||||||
label: Codesandbox/StackBlitz link
|
|
||||||
description: |
|
|
||||||
A link to a CodeSandbox or StackBlitz that includes a minimal reproduction of the problem. In rare cases when not applicable, you can link to a GitHub repository that we can easily run to recreate the issue. If a report is vague and does not have a reproduction, it will be closed without warning.
|
|
||||||
|
|
||||||
> [!CAUTION]
|
|
||||||
> If you skip this step, this issue might be **labeled** with `please add a reproduction` and **closed**.
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: logs
|
|
||||||
attributes:
|
|
||||||
label: Logs
|
|
||||||
description: "Please include browser console and server logs around the time this bug occurred. Optional if provided reproduction. Please try not to insert an image but copy paste the log text."
|
|
||||||
render: bash
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: system-info
|
|
||||||
attributes:
|
|
||||||
label: System Info
|
|
||||||
description: Information about browsers, system or binaries that's relevant.
|
|
||||||
render: bash
|
|
||||||
placeholder: System, Binaries, Browsers
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: checkboxes
|
|
||||||
id: terms
|
|
||||||
attributes:
|
|
||||||
label: Before submitting
|
|
||||||
description: By submitting this issue, you agree to follow our [Contributing Guidelines](https://github.com/shadcn-ui/ui/blob/main/CONTRIBUTING.md).
|
|
||||||
options:
|
|
||||||
- label: I've made research efforts and searched the documentation
|
|
||||||
required: true
|
|
||||||
- label: I've searched for existing issues
|
|
||||||
required: true
|
|
||||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
5
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +0,0 @@
|
|||||||
blank_issues_enabled: false
|
|
||||||
contact_links:
|
|
||||||
- name: Get Help
|
|
||||||
url: https://github.com/shadcn-ui/ui/discussions/new?category=general
|
|
||||||
about: If you can't get something to work the way you expect, open a question in our discussion forums.
|
|
||||||
55
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
55
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -1,55 +0,0 @@
|
|||||||
name: "Feature request"
|
|
||||||
description: Create a feature request for shadcn/ui
|
|
||||||
title: '[feat]: '
|
|
||||||
labels: ['area: request']
|
|
||||||
body:
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
### Thanks for taking the time to create a feature request! Please search open/closed issues before submitting, as the issue may have already been reported/addressed.
|
|
||||||
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
#### If you aren't sure this is a bug or not, please open a discussion instead:
|
|
||||||
- [Discussions](https://github.com/shadcn-ui/ui/discussions/new?category=general)
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: feature-description
|
|
||||||
attributes:
|
|
||||||
label: Feature description
|
|
||||||
description: Tell us about your feature request
|
|
||||||
placeholder: 'I think this feature would be great because...'
|
|
||||||
value: 'Describe your feature request...'
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: input
|
|
||||||
id: components-affected
|
|
||||||
attributes:
|
|
||||||
label: Affected component/components
|
|
||||||
description: Is this feature request relevant to any of the already existing components?
|
|
||||||
placeholder: ex. Button, Checkbox...
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: context
|
|
||||||
attributes:
|
|
||||||
label: Additional Context
|
|
||||||
description: Add any other context about the feature here.
|
|
||||||
placeholder: ex. screenshots, Stack Overflow links, forum links, etc.
|
|
||||||
value: 'Additional details here...'
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
|
|
||||||
- type: checkboxes
|
|
||||||
id: terms
|
|
||||||
attributes:
|
|
||||||
label: Before submitting
|
|
||||||
description: By submitting this issue, you agree to follow our [Contributing Guidelines](https://github.com/shadcn-ui/ui/blob/main/CONTRIBUTING.md).
|
|
||||||
options:
|
|
||||||
- label: I've made research efforts and searched the documentation
|
|
||||||
required: true
|
|
||||||
- label: I've searched for existing issues and PRs
|
|
||||||
required: true
|
|
||||||
6
.github/dependabot.yml
vendored
6
.github/dependabot.yml
vendored
@@ -1,6 +0,0 @@
|
|||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: "npm"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "weekly"
|
|
||||||
2
.github/version-script-beta.js
vendored
2
.github/version-script-beta.js
vendored
@@ -4,7 +4,7 @@
|
|||||||
import { exec } from "child_process"
|
import { exec } from "child_process"
|
||||||
import fs from "fs"
|
import fs from "fs"
|
||||||
|
|
||||||
const pkgJsonPath = "packages/shadcn/package.json"
|
const pkgJsonPath = "packages/cli/package.json"
|
||||||
try {
|
try {
|
||||||
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath))
|
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath))
|
||||||
exec("git rev-parse --short HEAD", (err, stdout) => {
|
exec("git rev-parse --short HEAD", (err, stdout) => {
|
||||||
|
|||||||
2
.github/version-script-next.js
vendored
2
.github/version-script-next.js
vendored
@@ -4,7 +4,7 @@
|
|||||||
import { exec } from "child_process"
|
import { exec } from "child_process"
|
||||||
import fs from "fs"
|
import fs from "fs"
|
||||||
|
|
||||||
const pkgJsonPath = "packages/shadcn/package.json"
|
const pkgJsonPath = "packages/cli/package.json"
|
||||||
try {
|
try {
|
||||||
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath))
|
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath))
|
||||||
exec("git rev-parse --short HEAD", (err, stdout) => {
|
exec("git rev-parse --short HEAD", (err, stdout) => {
|
||||||
|
|||||||
21
.github/workflows/code-check.yml
vendored
21
.github/workflows/code-check.yml
vendored
@@ -16,13 +16,13 @@ jobs:
|
|||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 18
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v2.2.4
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
id: pnpm-install
|
id: pnpm-install
|
||||||
with:
|
with:
|
||||||
version: 9.0.6
|
version: 8.6.1
|
||||||
run_install: false
|
run_install: false
|
||||||
|
|
||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
@@ -52,13 +52,13 @@ jobs:
|
|||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 18
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v2.2.4
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
id: pnpm-install
|
id: pnpm-install
|
||||||
with:
|
with:
|
||||||
version: 9.0.6
|
version: 8.6.1
|
||||||
run_install: false
|
run_install: false
|
||||||
|
|
||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
@@ -90,13 +90,13 @@ jobs:
|
|||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 18
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v2.2.4
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
id: pnpm-install
|
id: pnpm-install
|
||||||
with:
|
with:
|
||||||
version: 9.0.6
|
version: 8.6.1
|
||||||
run_install: false
|
run_install: false
|
||||||
|
|
||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
@@ -113,7 +113,4 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
|
||||||
- name: Build packages
|
|
||||||
run: pnpm --filter=shadcn build
|
|
||||||
|
|
||||||
- run: pnpm typecheck
|
- run: pnpm typecheck
|
||||||
|
|||||||
45
.github/workflows/issue-stale.yml
vendored
45
.github/workflows/issue-stale.yml
vendored
@@ -1,45 +0,0 @@
|
|||||||
# Adapted from vercel/next.js
|
|
||||||
name: "Stale issue handler"
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
schedule:
|
|
||||||
# This runs every day 20 minutes before midnight: https://crontab.guru/#40_23_*_*_*
|
|
||||||
- cron: "40 23 * * *"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
stale:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.repository_owner == 'shadcn-ui'
|
|
||||||
steps:
|
|
||||||
- uses: actions/stale@v9
|
|
||||||
id: issue-stale
|
|
||||||
name: "Mark stale issues, close stale issues"
|
|
||||||
with:
|
|
||||||
repo-token: ${{ secrets.STALE_TOKEN }}
|
|
||||||
ascending: true
|
|
||||||
days-before-issue-close: 7
|
|
||||||
days-before-issue-stale: 365 # ~2 years
|
|
||||||
days-before-pr-stale: -1
|
|
||||||
days-before-pr-close: -1
|
|
||||||
remove-issue-stale-when-updated: true
|
|
||||||
stale-issue-label: "stale?"
|
|
||||||
exempt-issue-labels: "roadmap,next,bug"
|
|
||||||
stale-issue-message: "This issue has been automatically marked as stale due to one year of inactivity. It will be closed in 7 days unless there’s further input. If you believe this issue is still relevant, please leave a comment or provide updated details. Thank you."
|
|
||||||
close-issue-message: "This issue has been automatically closed due to one year of inactivity. If you’re still experiencing a similar problem or have additional details to share, please open a new issue following our current issue template. Your updated report helps us investigate and address concerns more efficiently. Thank you for your understanding!"
|
|
||||||
operations-per-run: 300 # 1 operation per 100 issues, the rest is to label/comment/close
|
|
||||||
- uses: actions/stale@v9
|
|
||||||
id: pr-state
|
|
||||||
name: "Mark stale PRs, close stale PRs"
|
|
||||||
with:
|
|
||||||
repo-token: ${{ secrets.STALE_TOKEN }}
|
|
||||||
ascending: true
|
|
||||||
days-before-issue-close: -1
|
|
||||||
days-before-issue-stale: -1
|
|
||||||
days-before-pr-close: 7
|
|
||||||
days-before-pr-stale: 365 # PRs with no activity in over 90 days will be marked as stale
|
|
||||||
remove-pr-stale-when-updated: true
|
|
||||||
exempt-pr-labels: "roadmap,nex,awaiting-approval,work-in-progress"
|
|
||||||
stale-pr-label: "stale?"
|
|
||||||
stale-pr-message: "This PR has been automatically marked as stale due to one year of inactivity. It will be closed in 7 days unless there’s further input. If you believe this PR is still relevant, please leave a comment or provide updated details. Thank you."
|
|
||||||
close-pr-message: "This PR has been automatically closed due to one year of inactivity. Thank you for your understanding!"
|
|
||||||
operations-per-run: 300 # 1 operation per 100 issues, the rest is to label/comment/close
|
|
||||||
6
.github/workflows/prerelease-comment.yml
vendored
6
.github/workflows/prerelease-comment.yml
vendored
@@ -28,8 +28,8 @@ jobs:
|
|||||||
|
|
||||||
for (const artifact of allArtifacts.data.artifacts) {
|
for (const artifact of allArtifacts.data.artifacts) {
|
||||||
// Extract the PR number and package version from the artifact name
|
// Extract the PR number and package version from the artifact name
|
||||||
const match = /^npm-package-shadcn@(.*?)-pr-(\d+)/.exec(artifact.name);
|
const match = /^npm-package-shadcn-ui@(.*?)-pr-(\d+)/.exec(artifact.name);
|
||||||
|
|
||||||
if (match) {
|
if (match) {
|
||||||
require("fs").appendFileSync(
|
require("fs").appendFileSync(
|
||||||
process.env.GITHUB_ENV,
|
process.env.GITHUB_ENV,
|
||||||
@@ -49,7 +49,7 @@ jobs:
|
|||||||
A new prerelease is available for testing:
|
A new prerelease is available for testing:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pnpm dlx shadcn@${{ env.BETA_PACKAGE_VERSION }}
|
npx shadcn-ui@${{ env.BETA_PACKAGE_VERSION }}
|
||||||
```
|
```
|
||||||
|
|
||||||
- name: "Remove the autorelease label once published"
|
- name: "Remove the autorelease label once published"
|
||||||
|
|||||||
14
.github/workflows/prerelease.yml
vendored
14
.github/workflows/prerelease.yml
vendored
@@ -23,9 +23,9 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Use PNPM
|
- name: Use PNPM
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v2.2.4
|
||||||
with:
|
with:
|
||||||
version: 9.0.6
|
version: 8.6.1
|
||||||
|
|
||||||
- name: Use Node.js 18
|
- name: Use Node.js 18
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
@@ -40,7 +40,7 @@ jobs:
|
|||||||
run: node .github/version-script-beta.js
|
run: node .github/version-script-beta.js
|
||||||
|
|
||||||
- name: Authenticate to NPM
|
- name: Authenticate to NPM
|
||||||
run: echo "//registry.npmjs.org/:_authToken=$NPM_ACCESS_TOKEN" >> packages/shadcn/.npmrc
|
run: echo "//registry.npmjs.org/:_authToken=$NPM_ACCESS_TOKEN" >> packages/cli/.npmrc
|
||||||
env:
|
env:
|
||||||
NPM_ACCESS_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
|
NPM_ACCESS_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
|
||||||
|
|
||||||
@@ -51,10 +51,10 @@ jobs:
|
|||||||
id: package-version
|
id: package-version
|
||||||
uses: martinbeentjes/npm-get-version-action@main
|
uses: martinbeentjes/npm-get-version-action@main
|
||||||
with:
|
with:
|
||||||
path: packages/shadcn
|
path: packages/cli
|
||||||
|
|
||||||
- name: Upload packaged artifact
|
- name: Upload packaged artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: npm-package-shadcn@${{ steps.package-version.outputs.current-version }}-pr-${{ github.event.number }} # encode the PR number into the artifact name
|
name: npm-package-shadcn-ui@${{ steps.package-version.outputs.current-version }}-pr-${{ github.event.number }} # encode the PR number into the artifact name
|
||||||
path: packages/shadcn/dist/index.js
|
path: packages/cli/dist/index.js
|
||||||
|
|||||||
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -19,14 +19,14 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Use PNPM
|
- name: Use PNPM
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v2.2.4
|
||||||
with:
|
with:
|
||||||
version: 9.0.6
|
version: 8.6.1
|
||||||
|
|
||||||
- name: Use Node.js 18
|
- name: Use Node.js 18
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
version: 9.0.6
|
version: 8.6.1
|
||||||
node-version: 18
|
node-version: 18
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ jobs:
|
|||||||
# run: pnpm check
|
# run: pnpm check
|
||||||
|
|
||||||
- name: Build the package
|
- name: Build the package
|
||||||
run: pnpm shadcn:build
|
run: pnpm build:cli
|
||||||
|
|
||||||
- name: Create Version PR or Publish to NPM
|
- name: Create Version PR or Publish to NPM
|
||||||
id: changesets
|
id: changesets
|
||||||
|
|||||||
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
@@ -18,11 +18,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 18
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v2.2.4
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
id: pnpm-install
|
id: pnpm-install
|
||||||
with:
|
with:
|
||||||
version: 9.0.6
|
version: 8.6.1
|
||||||
run_install: false
|
run_install: false
|
||||||
|
|
||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
|
|||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -33,9 +33,4 @@ yarn-error.log*
|
|||||||
.turbo
|
.turbo
|
||||||
|
|
||||||
.contentlayer
|
.contentlayer
|
||||||
tsconfig.tsbuildinfo
|
tsconfig.tsbuildinfo
|
||||||
|
|
||||||
# ide
|
|
||||||
.idea
|
|
||||||
.fleet
|
|
||||||
.vscode
|
|
||||||
4
.husky/commit-msg
Executable file
4
.husky/commit-msg
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
. "$(dirname -- "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
npx commitlint --edit $1
|
||||||
1
.npmrc
1
.npmrc
@@ -1,2 +1 @@
|
|||||||
auto-install-peers=true
|
auto-install-peers=true
|
||||||
link-workspace-packages=true
|
|
||||||
|
|||||||
@@ -3,5 +3,4 @@ node_modules
|
|||||||
.next
|
.next
|
||||||
build
|
build
|
||||||
.contentlayer
|
.contentlayer
|
||||||
apps/www/pages/api/registry.json
|
apps/www/pages/api/registry.json
|
||||||
**/fixtures
|
|
||||||
11
.vscode/settings.json
vendored
11
.vscode/settings.json
vendored
@@ -4,14 +4,7 @@
|
|||||||
{ "pattern": "packages/*/" }
|
{ "pattern": "packages/*/" }
|
||||||
],
|
],
|
||||||
"tailwindCSS.experimental.classRegex": [
|
"tailwindCSS.experimental.classRegex": [
|
||||||
["cva\\(((?:[^()]|\\([^()]*\\))*)\\)", "[\"'`]?([^\"'`]+)[\"'`]?"],
|
["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
|
||||||
["cn\\(((?:[^()]|\\([^()]*\\))*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
|
["cn\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]
|
||||||
// "cva\\(([^)]*)\\)",
|
|
||||||
// "[\"'`]([^\"'`]*).*?[\"'`]"
|
|
||||||
],
|
|
||||||
"vitest.debugExclude": [
|
|
||||||
"<node_internals>/**",
|
|
||||||
"**/node_modules/**",
|
|
||||||
"**/fixtures/**"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,42 +91,6 @@ pnpm --filter=www dev
|
|||||||
pnpm --filter=shadcn-ui dev
|
pnpm --filter=shadcn-ui dev
|
||||||
```
|
```
|
||||||
|
|
||||||
## Running the CLI Locally
|
|
||||||
|
|
||||||
To run the CLI locally, you can follow the workflow:
|
|
||||||
|
|
||||||
1. Start by running the registry (main site) to make sure the components are up to date:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm www:dev
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Run the development script for the CLI:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm shadcn:dev
|
|
||||||
```
|
|
||||||
|
|
||||||
3. In another terminal tab, test the CLI by running:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm shadcn
|
|
||||||
```
|
|
||||||
|
|
||||||
To test the CLI in a specific app, use a command like:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm shadcn <init | add | ...> -c ~/Desktop/my-app
|
|
||||||
```
|
|
||||||
|
|
||||||
4. To run the tests for the CLI:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm --filter=shadcn test
|
|
||||||
```
|
|
||||||
|
|
||||||
This workflow ensures that you are running the most recent version of the registry and testing the CLI properly in your local environment.
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
The documentation for this project is located in the `www` workspace. You can run the documentation locally by running the following command:
|
The documentation for this project is located in the `www` workspace. You can run the documentation locally by running the following command:
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
# Security Policy
|
|
||||||
|
|
||||||
If you believe you have found a security vulnerability, we encourage you to let us know right away.
|
|
||||||
|
|
||||||
We will investigate all legitimate reports and do our best to quickly fix the problem.
|
|
||||||
|
|
||||||
Our preference is that you make use of GitHub's private vulnerability reporting feature to disclose potential security vulnerabilities in our Open Source Software.
|
|
||||||
|
|
||||||
To do this, please visit the security tab of the repository and click the "Report a vulnerability" button.
|
|
||||||
41
apps/v4/.gitignore
vendored
41
apps/v4/.gitignore
vendored
@@ -1,41 +0,0 @@
|
|||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
||||||
|
|
||||||
# dependencies
|
|
||||||
/node_modules
|
|
||||||
/.pnp
|
|
||||||
.pnp.*
|
|
||||||
.yarn/*
|
|
||||||
!.yarn/patches
|
|
||||||
!.yarn/plugins
|
|
||||||
!.yarn/releases
|
|
||||||
!.yarn/versions
|
|
||||||
|
|
||||||
# testing
|
|
||||||
/coverage
|
|
||||||
|
|
||||||
# next.js
|
|
||||||
/.next/
|
|
||||||
/out/
|
|
||||||
|
|
||||||
# production
|
|
||||||
/build
|
|
||||||
|
|
||||||
# misc
|
|
||||||
.DS_Store
|
|
||||||
*.pem
|
|
||||||
|
|
||||||
# debug
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
.pnpm-debug.log*
|
|
||||||
|
|
||||||
# env files (can opt-in for committing if needed)
|
|
||||||
.env*
|
|
||||||
|
|
||||||
# vercel
|
|
||||||
.vercel
|
|
||||||
|
|
||||||
# typescript
|
|
||||||
*.tsbuildinfo
|
|
||||||
next-env.d.ts
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
dist
|
|
||||||
node_modules
|
|
||||||
.next
|
|
||||||
build
|
|
||||||
.contentlayer
|
|
||||||
registry/__index__.tsx
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
This is a wip registry for the `shadcn` canary version. It has React 19 and Tailwind v4 components.
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// The content of this directory is autogenerated by the registry server.
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
> Files inside this directory is autogenerated by `./scripts/build-registry.ts`. **Do not edit them manually.** - shadcn
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,76 +0,0 @@
|
|||||||
export { ChartAreaDefault } from "@/registry/new-york-v4/charts/chart-area-default"
|
|
||||||
export { ChartAreaLinear } from "@/registry/new-york-v4/charts/chart-area-linear"
|
|
||||||
export { ChartAreaStep } from "@/registry/new-york-v4/charts/chart-area-step"
|
|
||||||
export { ChartAreaLegend } from "@/registry/new-york-v4/charts/chart-area-legend"
|
|
||||||
export { ChartAreaStacked } from "@/registry/new-york-v4/charts/chart-area-stacked"
|
|
||||||
export { ChartAreaStackedExpand } from "@/registry/new-york-v4/charts/chart-area-stacked-expand"
|
|
||||||
export { ChartAreaIcons } from "@/registry/new-york-v4/charts/chart-area-icons"
|
|
||||||
export { ChartAreaGradient } from "@/registry/new-york-v4/charts/chart-area-gradient"
|
|
||||||
export { ChartAreaAxes } from "@/registry/new-york-v4/charts/chart-area-axes"
|
|
||||||
export { ChartAreaInteractive } from "@/registry/new-york-v4/charts/chart-area-interactive"
|
|
||||||
|
|
||||||
export { ChartBarDefault } from "@/registry/new-york-v4/charts/chart-bar-default"
|
|
||||||
export { ChartBarHorizontal } from "@/registry/new-york-v4/charts/chart-bar-horizontal"
|
|
||||||
export { ChartBarMultiple } from "@/registry/new-york-v4/charts/chart-bar-multiple"
|
|
||||||
export { ChartBarStacked } from "@/registry/new-york-v4/charts/chart-bar-stacked"
|
|
||||||
export { ChartBarLabel } from "@/registry/new-york-v4/charts/chart-bar-label"
|
|
||||||
export { ChartBarLabelCustom } from "@/registry/new-york-v4/charts/chart-bar-label-custom"
|
|
||||||
export { ChartBarMixed } from "@/registry/new-york-v4/charts/chart-bar-mixed"
|
|
||||||
export { ChartBarActive } from "@/registry/new-york-v4/charts/chart-bar-active"
|
|
||||||
export { ChartBarNegative } from "@/registry/new-york-v4/charts/chart-bar-negative"
|
|
||||||
export { ChartBarInteractive } from "@/registry/new-york-v4/charts/chart-bar-interactive"
|
|
||||||
|
|
||||||
export { ChartLineDefault } from "@/registry/new-york-v4/charts/chart-line-default"
|
|
||||||
export { ChartLineLinear } from "@/registry/new-york-v4/charts/chart-line-linear"
|
|
||||||
export { ChartLineStep } from "@/registry/new-york-v4/charts/chart-line-step"
|
|
||||||
export { ChartLineMultiple } from "@/registry/new-york-v4/charts/chart-line-multiple"
|
|
||||||
export { ChartLineDots } from "@/registry/new-york-v4/charts/chart-line-dots"
|
|
||||||
export { ChartLineDotsCustom } from "@/registry/new-york-v4/charts/chart-line-dots-custom"
|
|
||||||
export { ChartLineDotsColors } from "@/registry/new-york-v4/charts/chart-line-dots-colors"
|
|
||||||
export { ChartLineLabel } from "@/registry/new-york-v4/charts/chart-line-label"
|
|
||||||
export { ChartLineLabelCustom } from "@/registry/new-york-v4/charts/chart-line-label-custom"
|
|
||||||
export { ChartLineInteractive } from "@/registry/new-york-v4/charts/chart-line-interactive"
|
|
||||||
|
|
||||||
export { ChartPieSimple } from "@/registry/new-york-v4/charts/chart-pie-simple"
|
|
||||||
export { ChartPieSeparatorNone } from "@/registry/new-york-v4/charts/chart-pie-separator-none"
|
|
||||||
export { ChartPieLabel } from "@/registry/new-york-v4/charts/chart-pie-label"
|
|
||||||
export { ChartPieLabelCustom } from "@/registry/new-york-v4/charts/chart-pie-label-custom"
|
|
||||||
export { ChartPieLabelList } from "@/registry/new-york-v4/charts/chart-pie-label-list"
|
|
||||||
export { ChartPieLegend } from "@/registry/new-york-v4/charts/chart-pie-legend"
|
|
||||||
export { ChartPieDonut } from "@/registry/new-york-v4/charts/chart-pie-donut"
|
|
||||||
export { ChartPieDonutActive } from "@/registry/new-york-v4/charts/chart-pie-donut-active"
|
|
||||||
export { ChartPieDonutText } from "@/registry/new-york-v4/charts/chart-pie-donut-text"
|
|
||||||
export { ChartPieStacked } from "@/registry/new-york-v4/charts/chart-pie-stacked"
|
|
||||||
export { ChartPieInteractive } from "@/registry/new-york-v4/charts/chart-pie-interactive"
|
|
||||||
|
|
||||||
export { ChartRadarDefault } from "@/registry/new-york-v4/charts/chart-radar-default"
|
|
||||||
export { ChartRadarDots } from "@/registry/new-york-v4/charts/chart-radar-dots"
|
|
||||||
export { ChartRadarLinesOnly } from "@/registry/new-york-v4/charts/chart-radar-lines-only"
|
|
||||||
export { ChartRadarLabelCustom } from "@/registry/new-york-v4/charts/chart-radar-label-custom"
|
|
||||||
export { ChartRadarGridCustom } from "@/registry/new-york-v4/charts/chart-radar-grid-custom"
|
|
||||||
export { ChartRadarGridNone } from "@/registry/new-york-v4/charts/chart-radar-grid-none"
|
|
||||||
export { ChartRadarGridCircle } from "@/registry/new-york-v4/charts/chart-radar-grid-circle"
|
|
||||||
export { ChartRadarGridCircleNoLines } from "@/registry/new-york-v4/charts/chart-radar-grid-circle-no-lines"
|
|
||||||
export { ChartRadarGridCircleFill } from "@/registry/new-york-v4/charts/chart-radar-grid-circle-fill"
|
|
||||||
export { ChartRadarGridFill } from "@/registry/new-york-v4/charts/chart-radar-grid-fill"
|
|
||||||
export { ChartRadarMultiple } from "@/registry/new-york-v4/charts/chart-radar-multiple"
|
|
||||||
export { ChartRadarLegend } from "@/registry/new-york-v4/charts/chart-radar-legend"
|
|
||||||
export { ChartRadarIcons } from "@/registry/new-york-v4/charts/chart-radar-icons"
|
|
||||||
export { ChartRadarRadius } from "@/registry/new-york-v4/charts/chart-radar-radius"
|
|
||||||
|
|
||||||
export { ChartRadialSimple } from "@/registry/new-york-v4/charts/chart-radial-simple"
|
|
||||||
export { ChartRadialLabel } from "@/registry/new-york-v4/charts/chart-radial-label"
|
|
||||||
export { ChartRadialGrid } from "@/registry/new-york-v4/charts/chart-radial-grid"
|
|
||||||
export { ChartRadialText } from "@/registry/new-york-v4/charts/chart-radial-text"
|
|
||||||
export { ChartRadialShape } from "@/registry/new-york-v4/charts/chart-radial-shape"
|
|
||||||
export { ChartRadialStacked } from "@/registry/new-york-v4/charts/chart-radial-stacked"
|
|
||||||
|
|
||||||
export { ChartTooltipDefault } from "@/registry/new-york-v4/charts/chart-tooltip-default"
|
|
||||||
export { ChartTooltipIndicatorLine } from "@/registry/new-york-v4/charts/chart-tooltip-indicator-line"
|
|
||||||
export { ChartTooltipIndicatorNone } from "@/registry/new-york-v4/charts/chart-tooltip-indicator-none"
|
|
||||||
export { ChartTooltipLabelCustom } from "@/registry/new-york-v4/charts/chart-tooltip-label-custom"
|
|
||||||
export { ChartTooltipLabelFormatter } from "@/registry/new-york-v4/charts/chart-tooltip-label-formatter"
|
|
||||||
export { ChartTooltipLabelNone } from "@/registry/new-york-v4/charts/chart-tooltip-label-none"
|
|
||||||
export { ChartTooltipFormatter } from "@/registry/new-york-v4/charts/chart-tooltip-formatter"
|
|
||||||
export { ChartTooltipIcons } from "@/registry/new-york-v4/charts/chart-tooltip-icons"
|
|
||||||
export { ChartTooltipAdvanced } from "@/registry/new-york-v4/charts/chart-tooltip-advanced"
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import { ComponentWrapper } from "@/components/component-wrapper"
|
|
||||||
import * as Charts from "@/app/(app)/charts/charts"
|
|
||||||
|
|
||||||
export default function ChartsPage() {
|
|
||||||
return (
|
|
||||||
<div className="grid flex-1 grid-cols-3 items-start gap-4 p-4 2xl:grid-cols-4">
|
|
||||||
{Object.entries(Charts)
|
|
||||||
.sort()
|
|
||||||
.map(([key, Component]) => (
|
|
||||||
<ComponentWrapper
|
|
||||||
key={key}
|
|
||||||
name={key}
|
|
||||||
className="w-auto data-[name=chartareainteractive]:col-span-3 data-[name=chartbarinteractive]:col-span-3 data-[name=chartlineinteractive]:col-span-3 **:data-[slot=card]:w-full"
|
|
||||||
>
|
|
||||||
<Component />
|
|
||||||
</ComponentWrapper>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import { FormsDemo } from "@/components/forms-demo"
|
|
||||||
|
|
||||||
export default function FormsPage() {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-1 flex-col items-center justify-center gap-12 p-4 lg:flex-row">
|
|
||||||
<div className="">
|
|
||||||
<FormsDemo />
|
|
||||||
</div>
|
|
||||||
<div className="theme-scaled">
|
|
||||||
<FormsDemo />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
import { cookies } from "next/headers"
|
|
||||||
|
|
||||||
import { AppSidebar } from "@/components/app-sidebar"
|
|
||||||
import { ModeSwitcher } from "@/components/mode-switcher"
|
|
||||||
import { NavHeader } from "@/components/nav-header"
|
|
||||||
import { ThemeSelector } from "@/components/theme-selector"
|
|
||||||
import { Separator } from "@/registry/new-york-v4/ui/separator"
|
|
||||||
import {
|
|
||||||
SidebarInset,
|
|
||||||
SidebarProvider,
|
|
||||||
SidebarTrigger,
|
|
||||||
} from "@/registry/new-york-v4/ui/sidebar"
|
|
||||||
|
|
||||||
export default async function AppLayout({
|
|
||||||
children,
|
|
||||||
}: Readonly<{
|
|
||||||
children: React.ReactNode
|
|
||||||
}>) {
|
|
||||||
const cookieStore = await cookies()
|
|
||||||
const defaultOpen = cookieStore.get("sidebar_state")?.value === "true"
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SidebarProvider defaultOpen={defaultOpen}>
|
|
||||||
<AppSidebar />
|
|
||||||
<SidebarInset>
|
|
||||||
<header className="bg-background sticky inset-x-0 top-0 isolate z-10 flex shrink-0 items-center gap-2 border-b">
|
|
||||||
<div className="flex h-14 w-full items-center gap-2 px-4">
|
|
||||||
<SidebarTrigger className="-ml-1.5" />
|
|
||||||
<Separator
|
|
||||||
orientation="vertical"
|
|
||||||
className="mr-2 data-[orientation=vertical]:h-4"
|
|
||||||
/>
|
|
||||||
<NavHeader />
|
|
||||||
<div className="ml-auto flex items-center gap-2">
|
|
||||||
<ThemeSelector />
|
|
||||||
<ModeSwitcher />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
{children}
|
|
||||||
</SidebarInset>
|
|
||||||
</SidebarProvider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import {
|
|
||||||
Manrope as FontManrope,
|
|
||||||
Lexend as FontSans,
|
|
||||||
Newsreader as FontSerif,
|
|
||||||
} from "next/font/google"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
import { LoginForm } from "@/components/login-form"
|
|
||||||
|
|
||||||
const fontSans = FontSans({ subsets: ["latin"], variable: "--font-sans" })
|
|
||||||
const fontSerif = FontSerif({ subsets: ["latin"], variable: "--font-serif" })
|
|
||||||
const fontManrope = FontManrope({
|
|
||||||
subsets: ["latin"],
|
|
||||||
variable: "--font-manrope",
|
|
||||||
})
|
|
||||||
export default function LoginPage() {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"bg-muted dark:bg-background flex flex-1 flex-col items-center justify-center gap-16 p-6 md:p-10",
|
|
||||||
fontSans.variable,
|
|
||||||
fontSerif.variable,
|
|
||||||
fontManrope.variable
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="w-full max-w-sm md:max-w-3xl">
|
|
||||||
<LoginForm />
|
|
||||||
</div>
|
|
||||||
<div className="theme-login-one w-full max-w-sm md:max-w-3xl">
|
|
||||||
<LoginForm imageUrl="https://images.unsplash.com/photo-1482872376051-5ce74ebf0908?q=80&w=3050&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" />
|
|
||||||
</div>
|
|
||||||
<div className="theme-login-two w-full max-w-sm md:max-w-3xl">
|
|
||||||
<LoginForm imageUrl="https://images.unsplash.com/photo-1498758536662-35b82cd15e29?q=80&w=3088&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" />
|
|
||||||
</div>
|
|
||||||
<div className="theme-login-three w-full max-w-sm md:max-w-3xl">
|
|
||||||
<LoginForm imageUrl="https://images.unsplash.com/photo-1536147116438-62679a5e01f2?q=80&w=2688&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
import { AccordionDemo } from "@/components/accordion-demo"
|
|
||||||
import { AlertDemo } from "@/components/alert-demo"
|
|
||||||
import { AlertDialogDemo } from "@/components/alert-dialog-demo"
|
|
||||||
import { AspectRatioDemo } from "@/components/aspect-ratio-demo"
|
|
||||||
import { AvatarDemo } from "@/components/avatar-demo"
|
|
||||||
import { BadgeDemo } from "@/components/badge-demo"
|
|
||||||
import { BreadcrumbDemo } from "@/components/breadcrumb-demo"
|
|
||||||
import { ButtonDemo } from "@/components/button-demo"
|
|
||||||
import { CalendarDemo } from "@/components/calendar-demo"
|
|
||||||
import { CardDemo } from "@/components/card-demo"
|
|
||||||
import { CarouselDemo } from "@/components/carousel-demo"
|
|
||||||
import { ChartDemo } from "@/components/chart-demo"
|
|
||||||
import { CheckboxDemo } from "@/components/checkbox-demo"
|
|
||||||
import { CollapsibleDemo } from "@/components/collapsible-demo"
|
|
||||||
import { ComboboxDemo } from "@/components/combobox-demo"
|
|
||||||
import { CommandDemo } from "@/components/command-demo"
|
|
||||||
import { ComponentWrapper } from "@/components/component-wrapper"
|
|
||||||
import { ContextMenuDemo } from "@/components/context-menu-demo"
|
|
||||||
import { DatePickerDemo } from "@/components/date-picker-demo"
|
|
||||||
import { DialogDemo } from "@/components/dialog-demo"
|
|
||||||
import { DrawerDemo } from "@/components/drawer-demo"
|
|
||||||
import { DropdownMenuDemo } from "@/components/dropdown-menu-demo"
|
|
||||||
import { FormDemo } from "@/components/form-demo"
|
|
||||||
import { HoverCardDemo } from "@/components/hover-card-demo"
|
|
||||||
import { InputDemo } from "@/components/input-demo"
|
|
||||||
import { InputOTPDemo } from "@/components/input-otp-demo"
|
|
||||||
import { LabelDemo } from "@/components/label-demo"
|
|
||||||
import { MenubarDemo } from "@/components/menubar-demo"
|
|
||||||
import { NavigationMenuDemo } from "@/components/navigation-menu-demo"
|
|
||||||
import { PaginationDemo } from "@/components/pagination-demo"
|
|
||||||
import { PopoverDemo } from "@/components/popover-demo"
|
|
||||||
import { ProgressDemo } from "@/components/progress-demo"
|
|
||||||
import { RadioGroupDemo } from "@/components/radio-group-demo"
|
|
||||||
import { ResizableDemo } from "@/components/resizable-demo"
|
|
||||||
import { ScrollAreaDemo } from "@/components/scroll-area-demo"
|
|
||||||
import { SelectDemo } from "@/components/select-demo"
|
|
||||||
import { SeparatorDemo } from "@/components/separator-demo"
|
|
||||||
import { SheetDemo } from "@/components/sheet-demo"
|
|
||||||
import { SkeletonDemo } from "@/components/skeleton-demo"
|
|
||||||
import { SliderDemo } from "@/components/slider-demo"
|
|
||||||
import { SonnerDemo } from "@/components/sonner-demo"
|
|
||||||
import { SwitchDemo } from "@/components/switch-demo"
|
|
||||||
import { TableDemo } from "@/components/table-demo"
|
|
||||||
import { TabsDemo } from "@/components/tabs-demo"
|
|
||||||
import { TextareaDemo } from "@/components/textarea-demo"
|
|
||||||
import { ToggleDemo } from "@/components/toggle-demo"
|
|
||||||
import { ToggleGroupDemo } from "@/components/toggle-group-demo"
|
|
||||||
import { TooltipDemo } from "@/components/tooltip-demo"
|
|
||||||
|
|
||||||
export default function SinkPage() {
|
|
||||||
return (
|
|
||||||
<div className="@container grid flex-1 gap-4 p-4">
|
|
||||||
<ComponentWrapper name="chart" className="w-full">
|
|
||||||
<ChartDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="accordion">
|
|
||||||
<AccordionDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="alert">
|
|
||||||
<AlertDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="alert-dialog">
|
|
||||||
<AlertDialogDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="aspect-ratio">
|
|
||||||
<AspectRatioDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="avatar">
|
|
||||||
<AvatarDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="badge">
|
|
||||||
<BadgeDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="breadcrumb">
|
|
||||||
<BreadcrumbDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="button">
|
|
||||||
<ButtonDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="calendar">
|
|
||||||
<CalendarDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="card">
|
|
||||||
<CardDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="carousel">
|
|
||||||
<CarouselDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="checkbox">
|
|
||||||
<CheckboxDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="collapsible">
|
|
||||||
<CollapsibleDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="combobox">
|
|
||||||
<ComboboxDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="command">
|
|
||||||
<CommandDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="context-menu">
|
|
||||||
<ContextMenuDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="date-picker">
|
|
||||||
<DatePickerDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="dialog">
|
|
||||||
<DialogDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="drawer">
|
|
||||||
<DrawerDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="dropdown-menu">
|
|
||||||
<DropdownMenuDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="form">
|
|
||||||
<FormDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="hover-card">
|
|
||||||
<HoverCardDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="input">
|
|
||||||
<InputDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="input-otp">
|
|
||||||
<InputOTPDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="label">
|
|
||||||
<LabelDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="menubar">
|
|
||||||
<MenubarDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="navigation-menu">
|
|
||||||
<NavigationMenuDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="pagination">
|
|
||||||
<PaginationDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="popover">
|
|
||||||
<PopoverDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="progress">
|
|
||||||
<ProgressDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="radio-group">
|
|
||||||
<RadioGroupDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="resizable">
|
|
||||||
<ResizableDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="scroll-area">
|
|
||||||
<ScrollAreaDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="select">
|
|
||||||
<SelectDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="separator">
|
|
||||||
<SeparatorDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="sheet">
|
|
||||||
<SheetDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="skeleton">
|
|
||||||
<SkeletonDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="slider">
|
|
||||||
<SliderDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="sonner">
|
|
||||||
<SonnerDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="switch">
|
|
||||||
<SwitchDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="table">
|
|
||||||
<TableDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="tabs">
|
|
||||||
<TabsDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="textarea">
|
|
||||||
<TextareaDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="toggle">
|
|
||||||
<ToggleDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="toggle-group">
|
|
||||||
<ToggleGroupDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
<ComponentWrapper name="tooltip">
|
|
||||||
<TooltipDemo />
|
|
||||||
</ComponentWrapper>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import { addDays, format } from "date-fns"
|
|
||||||
import { CalendarIcon } from "lucide-react"
|
|
||||||
import { DateRange } from "react-day-picker"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
|
||||||
import { Calendar } from "@/registry/new-york-v4/ui/calendar"
|
|
||||||
import {
|
|
||||||
Popover,
|
|
||||||
PopoverContent,
|
|
||||||
PopoverTrigger,
|
|
||||||
} from "@/registry/new-york-v4/ui/popover"
|
|
||||||
|
|
||||||
export function AnalyticsDatePicker() {
|
|
||||||
const [date, setDate] = React.useState<DateRange | undefined>({
|
|
||||||
from: new Date(new Date().getFullYear(), 0, 20),
|
|
||||||
to: addDays(new Date(new Date().getFullYear(), 0, 20), 20),
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Popover>
|
|
||||||
<PopoverTrigger asChild>
|
|
||||||
<Button
|
|
||||||
id="date"
|
|
||||||
variant="outline"
|
|
||||||
className={cn(
|
|
||||||
"w-fit justify-start px-2 font-normal",
|
|
||||||
!date && "text-muted-foreground"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<CalendarIcon className="text-muted-foreground" />
|
|
||||||
{date?.from ? (
|
|
||||||
date.to ? (
|
|
||||||
<>
|
|
||||||
{format(date.from, "LLL dd, y")} -{" "}
|
|
||||||
{format(date.to, "LLL dd, y")}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
format(date.from, "LLL dd, y")
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<span>Pick a date</span>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent className="w-auto p-0" align="end">
|
|
||||||
<Calendar
|
|
||||||
initialFocus
|
|
||||||
mode="range"
|
|
||||||
defaultMonth={date?.from}
|
|
||||||
selected={date}
|
|
||||||
onSelect={setDate}
|
|
||||||
numberOfMonths={2}
|
|
||||||
/>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import {
|
|
||||||
ChartLineIcon,
|
|
||||||
FileIcon,
|
|
||||||
HomeIcon,
|
|
||||||
LifeBuoy,
|
|
||||||
Send,
|
|
||||||
Settings2Icon,
|
|
||||||
ShoppingBagIcon,
|
|
||||||
ShoppingCartIcon,
|
|
||||||
UserIcon,
|
|
||||||
} from "lucide-react"
|
|
||||||
|
|
||||||
import { Sidebar, SidebarContent } from "@/registry/new-york-v4/ui/sidebar"
|
|
||||||
import { NavMain } from "@/app/(examples)/dashboard-03/components/nav-main"
|
|
||||||
import { NavSecondary } from "@/app/(examples)/dashboard-03/components/nav-secondary"
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
navMain: [
|
|
||||||
{
|
|
||||||
title: "Dashboard",
|
|
||||||
url: "/dashboard",
|
|
||||||
icon: HomeIcon,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Analytics",
|
|
||||||
url: "/dashboard/analytics",
|
|
||||||
icon: ChartLineIcon,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Orders",
|
|
||||||
url: "/dashboard/orders",
|
|
||||||
icon: ShoppingBagIcon,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Products",
|
|
||||||
url: "/dashboard/products",
|
|
||||||
icon: ShoppingCartIcon,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Invoices",
|
|
||||||
url: "/dashboard/invoices",
|
|
||||||
icon: FileIcon,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Customers",
|
|
||||||
url: "/dashboard/customers",
|
|
||||||
icon: UserIcon,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Settings",
|
|
||||||
url: "/dashboard/settings",
|
|
||||||
icon: Settings2Icon,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
navSecondary: [
|
|
||||||
{
|
|
||||||
title: "Support",
|
|
||||||
url: "#",
|
|
||||||
icon: LifeBuoy,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Feedback",
|
|
||||||
url: "#",
|
|
||||||
icon: Send,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
|
||||||
return (
|
|
||||||
<Sidebar
|
|
||||||
className="top-(--header-height) h-[calc(100svh-var(--header-height))]!"
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<SidebarContent>
|
|
||||||
<NavMain items={data.navMain} />
|
|
||||||
<NavSecondary items={data.navSecondary} className="mt-auto" />
|
|
||||||
</SidebarContent>
|
|
||||||
</Sidebar>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { TrendingUp } from "lucide-react"
|
|
||||||
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"
|
|
||||||
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardFooter,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from "@/registry/new-york-v4/ui/card"
|
|
||||||
import {
|
|
||||||
ChartConfig,
|
|
||||||
ChartContainer,
|
|
||||||
ChartTooltip,
|
|
||||||
ChartTooltipContent,
|
|
||||||
} from "@/registry/new-york-v4/ui/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: 346, mobile: 140 },
|
|
||||||
{ month: "July", desktop: 321, mobile: 275 },
|
|
||||||
{ month: "August", desktop: 132, mobile: 95 },
|
|
||||||
{ month: "September", desktop: 189, mobile: 225 },
|
|
||||||
{ month: "October", desktop: 302, mobile: 248 },
|
|
||||||
{ month: "November", desktop: 342, mobile: 285 },
|
|
||||||
{ month: "December", desktop: 328, mobile: 290 },
|
|
||||||
]
|
|
||||||
|
|
||||||
const chartConfig = {
|
|
||||||
desktop: {
|
|
||||||
label: "Desktop",
|
|
||||||
color: "var(--chart-1)",
|
|
||||||
},
|
|
||||||
mobile: {
|
|
||||||
label: "Mobile",
|
|
||||||
color: "var(--chart-2)",
|
|
||||||
},
|
|
||||||
} satisfies ChartConfig
|
|
||||||
|
|
||||||
export function ChartRevenue() {
|
|
||||||
return (
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardDescription>January - June 2024</CardDescription>
|
|
||||||
<CardTitle className="text-3xl font-bold tracking-tight">
|
|
||||||
$45,231.89
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<ChartContainer config={chartConfig} className="aspect-[3/1]">
|
|
||||||
<BarChart
|
|
||||||
accessibilityLayer
|
|
||||||
data={chartData}
|
|
||||||
margin={{
|
|
||||||
left: -16,
|
|
||||||
right: 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CartesianGrid vertical={false} />
|
|
||||||
<XAxis
|
|
||||||
dataKey="month"
|
|
||||||
tickLine={false}
|
|
||||||
tickMargin={10}
|
|
||||||
axisLine={false}
|
|
||||||
tickFormatter={(value) => value.slice(0, 3)}
|
|
||||||
/>
|
|
||||||
<YAxis
|
|
||||||
tickLine={false}
|
|
||||||
tickMargin={10}
|
|
||||||
axisLine={false}
|
|
||||||
tickFormatter={(value) => value.toLocaleString()}
|
|
||||||
domain={[0, "dataMax"]}
|
|
||||||
/>
|
|
||||||
<ChartTooltip
|
|
||||||
cursor={false}
|
|
||||||
content={<ChartTooltipContent hideIndicator />}
|
|
||||||
/>
|
|
||||||
<Bar
|
|
||||||
dataKey="desktop"
|
|
||||||
fill="var(--color-desktop)"
|
|
||||||
radius={[0, 0, 4, 4]}
|
|
||||||
stackId={1}
|
|
||||||
/>
|
|
||||||
<Bar
|
|
||||||
dataKey="mobile"
|
|
||||||
fill="var(--color-mobile)"
|
|
||||||
radius={[4, 4, 0, 0]}
|
|
||||||
stackId={1}
|
|
||||||
/>
|
|
||||||
</BarChart>
|
|
||||||
</ChartContainer>
|
|
||||||
</CardContent>
|
|
||||||
<CardFooter className="flex-col items-start gap-2 text-sm">
|
|
||||||
<div className="flex gap-2 leading-none font-medium">
|
|
||||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
|
||||||
</div>
|
|
||||||
<div className="text-muted-foreground leading-none">
|
|
||||||
Showing total visitors for the last 6 months
|
|
||||||
</div>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,189 +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,
|
|
||||||
CardAction,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from "@/registry/new-york-v4/ui/card"
|
|
||||||
import {
|
|
||||||
ChartConfig,
|
|
||||||
ChartContainer,
|
|
||||||
ChartStyle,
|
|
||||||
ChartTooltip,
|
|
||||||
ChartTooltipContent,
|
|
||||||
} from "@/registry/new-york-v4/ui/chart"
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@/registry/new-york-v4/ui/select"
|
|
||||||
|
|
||||||
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: "var(--chart-1)",
|
|
||||||
},
|
|
||||||
february: {
|
|
||||||
label: "February",
|
|
||||||
color: "var(--chart-2)",
|
|
||||||
},
|
|
||||||
march: {
|
|
||||||
label: "March",
|
|
||||||
color: "var(--chart-3)",
|
|
||||||
},
|
|
||||||
april: {
|
|
||||||
label: "April",
|
|
||||||
color: "var(--chart-4)",
|
|
||||||
},
|
|
||||||
may: {
|
|
||||||
label: "May",
|
|
||||||
color: "var(--chart-5)",
|
|
||||||
},
|
|
||||||
} satisfies ChartConfig
|
|
||||||
|
|
||||||
export function ChartVisitors() {
|
|
||||||
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}>
|
|
||||||
<ChartStyle id={id} config={chartConfig} />
|
|
||||||
<CardHeader>
|
|
||||||
<CardDescription>January - June 2024</CardDescription>
|
|
||||||
<CardTitle className="text-2xl font-bold">1,234 visitors</CardTitle>
|
|
||||||
<CardAction>
|
|
||||||
<Select value={activeMonth} onValueChange={setActiveMonth}>
|
|
||||||
<SelectTrigger
|
|
||||||
className="ml-auto h-8 w-[120px]"
|
|
||||||
aria-label="Select a value"
|
|
||||||
>
|
|
||||||
<SelectValue placeholder="Select month" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent align="end">
|
|
||||||
{months.map((key) => {
|
|
||||||
const config = chartConfig[key as keyof typeof chartConfig]
|
|
||||||
|
|
||||||
if (!config) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const color = "color" in config ? config.color : undefined
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SelectItem key={key} value={key}>
|
|
||||||
<div className="flex items-center gap-2 text-xs">
|
|
||||||
<span
|
|
||||||
className="flex h-3 w-3 shrink-0 rounded-sm"
|
|
||||||
style={{
|
|
||||||
backgroundColor: color,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{config?.label}
|
|
||||||
</div>
|
|
||||||
</SelectItem>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</CardAction>
|
|
||||||
</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,28 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import { MoonIcon, SunIcon } from "lucide-react"
|
|
||||||
import { useTheme } from "next-themes"
|
|
||||||
|
|
||||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
|
||||||
|
|
||||||
export function ModeToggle() {
|
|
||||||
const { setTheme, resolvedTheme } = useTheme()
|
|
||||||
|
|
||||||
const toggleTheme = React.useCallback(() => {
|
|
||||||
setTheme(resolvedTheme === "dark" ? "light" : "dark")
|
|
||||||
}, [resolvedTheme, setTheme])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
variant="secondary"
|
|
||||||
size="icon"
|
|
||||||
className="group/toggle size-8"
|
|
||||||
onClick={toggleTheme}
|
|
||||||
>
|
|
||||||
<SunIcon className="hidden [html.dark_&]:block" />
|
|
||||||
<MoonIcon className="hidden [html.light_&]:block" />
|
|
||||||
<span className="sr-only">Toggle theme</span>
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { usePathname } from "next/navigation"
|
|
||||||
import { ChevronRight, type LucideIcon } from "lucide-react"
|
|
||||||
|
|
||||||
import {
|
|
||||||
Collapsible,
|
|
||||||
CollapsibleContent,
|
|
||||||
CollapsibleTrigger,
|
|
||||||
} from "@/registry/new-york-v4/ui/collapsible"
|
|
||||||
import {
|
|
||||||
SidebarGroup,
|
|
||||||
SidebarGroupLabel,
|
|
||||||
SidebarMenu,
|
|
||||||
SidebarMenuAction,
|
|
||||||
SidebarMenuButton,
|
|
||||||
SidebarMenuItem,
|
|
||||||
SidebarMenuSub,
|
|
||||||
SidebarMenuSubButton,
|
|
||||||
SidebarMenuSubItem,
|
|
||||||
} from "@/registry/new-york-v4/ui/sidebar"
|
|
||||||
|
|
||||||
export function NavMain({
|
|
||||||
items,
|
|
||||||
}: {
|
|
||||||
items: {
|
|
||||||
title: string
|
|
||||||
url: string
|
|
||||||
icon: LucideIcon
|
|
||||||
isActive?: boolean
|
|
||||||
items?: {
|
|
||||||
title: string
|
|
||||||
url: string
|
|
||||||
}[]
|
|
||||||
disabled?: boolean
|
|
||||||
}[]
|
|
||||||
}) {
|
|
||||||
const pathname = usePathname()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SidebarGroup>
|
|
||||||
<SidebarGroupLabel>Dashboard</SidebarGroupLabel>
|
|
||||||
<SidebarMenu>
|
|
||||||
{items.map((item) => (
|
|
||||||
<Collapsible key={item.title} asChild defaultOpen={item.isActive}>
|
|
||||||
<SidebarMenuItem>
|
|
||||||
<SidebarMenuButton
|
|
||||||
asChild
|
|
||||||
tooltip={item.title}
|
|
||||||
isActive={pathname === item.url}
|
|
||||||
disabled={item.disabled}
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href={item.disabled ? "#" : item.url}
|
|
||||||
data-disabled={item.disabled}
|
|
||||||
className="data-[disabled=true]:opacity-50"
|
|
||||||
>
|
|
||||||
<item.icon className="text-muted-foreground" />
|
|
||||||
<span>{item.title}</span>
|
|
||||||
</a>
|
|
||||||
</SidebarMenuButton>
|
|
||||||
{item.items?.length ? (
|
|
||||||
<>
|
|
||||||
<CollapsibleTrigger asChild>
|
|
||||||
<SidebarMenuAction className="data-[state=open]:rotate-90">
|
|
||||||
<ChevronRight />
|
|
||||||
<span className="sr-only">Toggle</span>
|
|
||||||
</SidebarMenuAction>
|
|
||||||
</CollapsibleTrigger>
|
|
||||||
<CollapsibleContent>
|
|
||||||
<SidebarMenuSub>
|
|
||||||
{item.items?.map((subItem) => (
|
|
||||||
<SidebarMenuSubItem key={subItem.title}>
|
|
||||||
<SidebarMenuSubButton asChild>
|
|
||||||
<a href={subItem.url}>
|
|
||||||
<span>{subItem.title}</span>
|
|
||||||
</a>
|
|
||||||
</SidebarMenuSubButton>
|
|
||||||
</SidebarMenuSubItem>
|
|
||||||
))}
|
|
||||||
</SidebarMenuSub>
|
|
||||||
</CollapsibleContent>
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
</SidebarMenuItem>
|
|
||||||
</Collapsible>
|
|
||||||
))}
|
|
||||||
</SidebarMenu>
|
|
||||||
</SidebarGroup>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import * as React from "react"
|
|
||||||
import { type LucideIcon } from "lucide-react"
|
|
||||||
|
|
||||||
import {
|
|
||||||
SidebarGroup,
|
|
||||||
SidebarGroupContent,
|
|
||||||
SidebarMenu,
|
|
||||||
SidebarMenuButton,
|
|
||||||
SidebarMenuItem,
|
|
||||||
} from "@/registry/new-york-v4/ui/sidebar"
|
|
||||||
|
|
||||||
export function NavSecondary({
|
|
||||||
items,
|
|
||||||
...props
|
|
||||||
}: {
|
|
||||||
items: {
|
|
||||||
title: string
|
|
||||||
url: string
|
|
||||||
icon: LucideIcon
|
|
||||||
}[]
|
|
||||||
} & React.ComponentPropsWithoutRef<typeof SidebarGroup>) {
|
|
||||||
return (
|
|
||||||
<SidebarGroup {...props}>
|
|
||||||
<SidebarGroupContent>
|
|
||||||
<SidebarMenu>
|
|
||||||
{items.map((item) => (
|
|
||||||
<SidebarMenuItem key={item.title}>
|
|
||||||
<SidebarMenuButton asChild size="sm">
|
|
||||||
<a href={item.url}>
|
|
||||||
<item.icon />
|
|
||||||
<span>{item.title}</span>
|
|
||||||
</a>
|
|
||||||
</SidebarMenuButton>
|
|
||||||
</SidebarMenuItem>
|
|
||||||
))}
|
|
||||||
</SidebarMenu>
|
|
||||||
</SidebarGroupContent>
|
|
||||||
</SidebarGroup>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { BadgeCheck, Bell, CreditCard, LogOut, Sparkles } from "lucide-react"
|
|
||||||
|
|
||||||
import {
|
|
||||||
Avatar,
|
|
||||||
AvatarFallback,
|
|
||||||
AvatarImage,
|
|
||||||
} from "@/registry/new-york-v4/ui/avatar"
|
|
||||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuGroup,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuLabel,
|
|
||||||
DropdownMenuSeparator,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "@/registry/new-york-v4/ui/dropdown-menu"
|
|
||||||
|
|
||||||
export function NavUser({
|
|
||||||
user,
|
|
||||||
}: {
|
|
||||||
user: {
|
|
||||||
name: string
|
|
||||||
email: string
|
|
||||||
avatar: string
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button variant="ghost" size="icon">
|
|
||||||
<Avatar className="size-8 rounded-md">
|
|
||||||
<AvatarImage src={user.avatar} alt={user.name} />
|
|
||||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent
|
|
||||||
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
|
|
||||||
side="bottom"
|
|
||||||
align="end"
|
|
||||||
sideOffset={4}
|
|
||||||
>
|
|
||||||
<DropdownMenuLabel className="p-0 font-normal">
|
|
||||||
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
|
||||||
<Avatar className="h-8 w-8 rounded-lg">
|
|
||||||
<AvatarImage src={user.avatar} alt={user.name} />
|
|
||||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
|
||||||
<span className="truncate font-medium">{user.name}</span>
|
|
||||||
<span className="text-muted-foreground truncate text-xs">
|
|
||||||
{user.email}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</DropdownMenuLabel>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<DropdownMenuGroup>
|
|
||||||
<DropdownMenuItem>
|
|
||||||
<Sparkles />
|
|
||||||
Upgrade to Pro
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuGroup>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<DropdownMenuGroup>
|
|
||||||
<DropdownMenuItem>
|
|
||||||
<BadgeCheck />
|
|
||||||
Account
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem>
|
|
||||||
<CreditCard />
|
|
||||||
Billing
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem>
|
|
||||||
<Bell />
|
|
||||||
Notifications
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuGroup>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<DropdownMenuItem>
|
|
||||||
<LogOut />
|
|
||||||
Log out
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,217 +0,0 @@
|
|||||||
import {
|
|
||||||
ArrowUpDownIcon,
|
|
||||||
EllipsisVerticalIcon,
|
|
||||||
ListFilterIcon,
|
|
||||||
PlusIcon,
|
|
||||||
} from "lucide-react"
|
|
||||||
|
|
||||||
import { Badge } from "@/registry/new-york-v4/ui/badge"
|
|
||||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardFooter,
|
|
||||||
CardHeader,
|
|
||||||
} from "@/registry/new-york-v4/ui/card"
|
|
||||||
import { Checkbox } from "@/registry/new-york-v4/ui/checkbox"
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "@/registry/new-york-v4/ui/dropdown-menu"
|
|
||||||
import {
|
|
||||||
Pagination,
|
|
||||||
PaginationContent,
|
|
||||||
PaginationEllipsis,
|
|
||||||
PaginationItem,
|
|
||||||
PaginationLink,
|
|
||||||
PaginationNext,
|
|
||||||
PaginationPrevious,
|
|
||||||
} from "@/registry/new-york-v4/ui/pagination"
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@/registry/new-york-v4/ui/select"
|
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
} from "@/registry/new-york-v4/ui/table"
|
|
||||||
import { Tabs, TabsList, TabsTrigger } from "@/registry/new-york-v4/ui/tabs"
|
|
||||||
|
|
||||||
export function ProductsTable({
|
|
||||||
products,
|
|
||||||
}: {
|
|
||||||
products: {
|
|
||||||
id: string
|
|
||||||
name: string
|
|
||||||
price: number
|
|
||||||
stock: number
|
|
||||||
dateAdded: string
|
|
||||||
status: string
|
|
||||||
}[]
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<Card className="flex w-full flex-col gap-4">
|
|
||||||
<CardHeader className="flex flex-row items-center justify-between">
|
|
||||||
<Tabs defaultValue="all">
|
|
||||||
<TabsList className="w-full @3xl/page:w-fit">
|
|
||||||
<TabsTrigger value="all">All Products</TabsTrigger>
|
|
||||||
<TabsTrigger value="in-stock">In Stock</TabsTrigger>
|
|
||||||
<TabsTrigger value="low-stock">Low Stock</TabsTrigger>
|
|
||||||
<TabsTrigger value="add-product" asChild>
|
|
||||||
<button>
|
|
||||||
<PlusIcon />
|
|
||||||
</button>
|
|
||||||
</TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
</Tabs>
|
|
||||||
<div className="hidden items-center gap-2 **:data-[slot=button]:size-8 **:data-[slot=select-trigger]:h-8 @3xl/page:flex">
|
|
||||||
<Select defaultValue="all">
|
|
||||||
<SelectTrigger>
|
|
||||||
<span className="text-muted-foreground text-sm">Category:</span>
|
|
||||||
<SelectValue placeholder="Select a product" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="all">All</SelectItem>
|
|
||||||
<SelectItem value="in-stock">In Stock</SelectItem>
|
|
||||||
<SelectItem value="low-stock">Low Stock</SelectItem>
|
|
||||||
<SelectItem value="archived">Archived</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<Select defaultValue="all">
|
|
||||||
<SelectTrigger>
|
|
||||||
<span className="text-muted-foreground text-sm">Price:</span>
|
|
||||||
<SelectValue placeholder="Select a product" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="all">$100-$200</SelectItem>
|
|
||||||
<SelectItem value="in-stock">$200-$300</SelectItem>
|
|
||||||
<SelectItem value="low-stock">$300-$400</SelectItem>
|
|
||||||
<SelectItem value="archived">$400-$500</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<Select defaultValue="all">
|
|
||||||
<SelectTrigger>
|
|
||||||
<span className="text-muted-foreground text-sm">Status:</span>
|
|
||||||
<SelectValue placeholder="Select a product" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="all">In Stock</SelectItem>
|
|
||||||
<SelectItem value="in-stock">Low Stock</SelectItem>
|
|
||||||
<SelectItem value="low-stock">Archived</SelectItem>
|
|
||||||
<SelectItem value="archived">Archived</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<Button variant="outline" size="icon">
|
|
||||||
<ListFilterIcon />
|
|
||||||
</Button>
|
|
||||||
<Button variant="outline" size="icon">
|
|
||||||
<ArrowUpDownIcon />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<Table>
|
|
||||||
<TableHeader>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead className="w-12 px-4">
|
|
||||||
<Checkbox />
|
|
||||||
</TableHead>
|
|
||||||
<TableHead>Product</TableHead>
|
|
||||||
<TableHead className="text-right">Price</TableHead>
|
|
||||||
<TableHead className="text-right">Stock</TableHead>
|
|
||||||
<TableHead>Status</TableHead>
|
|
||||||
<TableHead>Date Added</TableHead>
|
|
||||||
<TableHead />
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody className="**:data-[slot=table-cell]:py-2.5">
|
|
||||||
{products.map((product) => (
|
|
||||||
<TableRow key={product.id}>
|
|
||||||
<TableCell className="px-4">
|
|
||||||
<Checkbox />
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="font-medium">{product.name}</TableCell>
|
|
||||||
<TableCell className="text-right">
|
|
||||||
${product.price.toFixed(2)}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="text-right">{product.stock}</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<Badge
|
|
||||||
variant="secondary"
|
|
||||||
className={
|
|
||||||
product.status === "Low Stock"
|
|
||||||
? "border-orange-700 bg-transparent text-orange-700 dark:border-orange-700 dark:bg-transparent dark:text-orange-700"
|
|
||||||
: "bg-green-100 text-green-800 dark:bg-green-950 dark:text-green-100"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{product.status}
|
|
||||||
</Badge>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
{new Date(product.dateAdded).toLocaleDateString("en-US", {
|
|
||||||
month: "long",
|
|
||||||
day: "numeric",
|
|
||||||
year: "numeric",
|
|
||||||
})}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button variant="ghost" size="icon" className="size-6">
|
|
||||||
<EllipsisVerticalIcon />
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align="end">
|
|
||||||
<DropdownMenuItem>Edit</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem variant="destructive">
|
|
||||||
Delete
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</CardContent>
|
|
||||||
<CardFooter className="flex flex-col items-center justify-between border-t pt-6 @3xl/page:flex-row">
|
|
||||||
<div className="text-muted-foreground hidden text-sm @3xl/page:block">
|
|
||||||
Showing 1-10 of 100 products
|
|
||||||
</div>
|
|
||||||
<Pagination className="mx-0 w-fit">
|
|
||||||
<PaginationContent>
|
|
||||||
<PaginationItem>
|
|
||||||
<PaginationPrevious href="#" />
|
|
||||||
</PaginationItem>
|
|
||||||
<PaginationItem>
|
|
||||||
<PaginationLink href="#">1</PaginationLink>
|
|
||||||
</PaginationItem>
|
|
||||||
<PaginationItem>
|
|
||||||
<PaginationLink href="#" isActive>
|
|
||||||
2
|
|
||||||
</PaginationLink>
|
|
||||||
</PaginationItem>
|
|
||||||
<PaginationItem>
|
|
||||||
<PaginationLink href="#">3</PaginationLink>
|
|
||||||
</PaginationItem>
|
|
||||||
<PaginationItem>
|
|
||||||
<PaginationEllipsis />
|
|
||||||
</PaginationItem>
|
|
||||||
<PaginationItem>
|
|
||||||
<PaginationNext href="#" />
|
|
||||||
</PaginationItem>
|
|
||||||
</PaginationContent>
|
|
||||||
</Pagination>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { Search } from "lucide-react"
|
|
||||||
|
|
||||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
|
||||||
import { SidebarInput } from "@/registry/new-york-v4/ui/sidebar"
|
|
||||||
|
|
||||||
export function SearchForm({ ...props }: React.ComponentProps<"form">) {
|
|
||||||
return (
|
|
||||||
<form {...props}>
|
|
||||||
<div className="relative">
|
|
||||||
<Label htmlFor="search" className="sr-only">
|
|
||||||
Search
|
|
||||||
</Label>
|
|
||||||
<SidebarInput
|
|
||||||
id="search"
|
|
||||||
placeholder="Type to search..."
|
|
||||||
className="h-8 pl-7"
|
|
||||||
/>
|
|
||||||
<Search className="pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 opacity-50 select-none" />
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { Fragment, useMemo } from "react"
|
|
||||||
import { usePathname } from "next/navigation"
|
|
||||||
import { SidebarIcon } from "lucide-react"
|
|
||||||
|
|
||||||
import { ThemeSelector } from "@/components/theme-selector"
|
|
||||||
import { SearchForm } from "@/registry/new-york-v4/blocks/sidebar-16/components/search-form"
|
|
||||||
import {
|
|
||||||
Breadcrumb,
|
|
||||||
BreadcrumbItem,
|
|
||||||
BreadcrumbLink,
|
|
||||||
BreadcrumbList,
|
|
||||||
BreadcrumbPage,
|
|
||||||
BreadcrumbSeparator,
|
|
||||||
} from "@/registry/new-york-v4/ui/breadcrumb"
|
|
||||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
|
||||||
import { Separator } from "@/registry/new-york-v4/ui/separator"
|
|
||||||
import { useSidebar } from "@/registry/new-york-v4/ui/sidebar"
|
|
||||||
import { ModeToggle } from "@/app/(examples)/dashboard-03/components/mode-toggle"
|
|
||||||
import { NavUser } from "@/app/(examples)/dashboard-03/components/nav-user"
|
|
||||||
|
|
||||||
export function SiteHeader() {
|
|
||||||
const { toggleSidebar } = useSidebar()
|
|
||||||
const pathname = usePathname()
|
|
||||||
|
|
||||||
// Faux breadcrumbs for demo.
|
|
||||||
const breadcrumbs = useMemo(() => {
|
|
||||||
return pathname
|
|
||||||
.split("/")
|
|
||||||
.filter((path) => path !== "")
|
|
||||||
.map((path, index, array) => ({
|
|
||||||
label: path,
|
|
||||||
href: `/${array.slice(0, index + 1).join("/")}`,
|
|
||||||
}))
|
|
||||||
}, [pathname])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<header
|
|
||||||
data-slot="site-header"
|
|
||||||
className="bg-background sticky top-0 z-50 flex w-full items-center border-b"
|
|
||||||
>
|
|
||||||
<div className="flex h-(--header-height) w-full items-center gap-2 px-2 pr-4">
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
onClick={toggleSidebar}
|
|
||||||
className="gap-2.5 has-[>svg]:px-2"
|
|
||||||
>
|
|
||||||
<SidebarIcon />
|
|
||||||
<span className="truncate font-medium">Acme Inc</span>
|
|
||||||
</Button>
|
|
||||||
<Separator
|
|
||||||
orientation="vertical"
|
|
||||||
className="mr-2 data-[orientation=vertical]:h-4"
|
|
||||||
/>
|
|
||||||
<Breadcrumb className="hidden sm:block">
|
|
||||||
<BreadcrumbList>
|
|
||||||
<BreadcrumbItem>
|
|
||||||
<BreadcrumbLink href="/" className="capitalize">
|
|
||||||
Home
|
|
||||||
</BreadcrumbLink>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
<BreadcrumbSeparator />
|
|
||||||
{breadcrumbs.map((breadcrumb, index) =>
|
|
||||||
index === breadcrumbs.length - 1 ? (
|
|
||||||
<BreadcrumbItem key={index}>
|
|
||||||
<BreadcrumbPage className="capitalize">
|
|
||||||
{breadcrumb.label}
|
|
||||||
</BreadcrumbPage>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
) : (
|
|
||||||
<Fragment key={index}>
|
|
||||||
<BreadcrumbItem>
|
|
||||||
<BreadcrumbLink
|
|
||||||
href={breadcrumb.href}
|
|
||||||
className="capitalize"
|
|
||||||
>
|
|
||||||
{breadcrumb.label}
|
|
||||||
</BreadcrumbLink>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
<BreadcrumbSeparator />
|
|
||||||
</Fragment>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</BreadcrumbList>
|
|
||||||
</Breadcrumb>
|
|
||||||
<div className="ml-auto flex items-center gap-2">
|
|
||||||
<SearchForm className="w-fullsm:w-auto" />
|
|
||||||
<ThemeSelector />
|
|
||||||
<ModeToggle />
|
|
||||||
<NavUser
|
|
||||||
user={{
|
|
||||||
name: "shadcn",
|
|
||||||
email: "m@example.com",
|
|
||||||
avatar: "/avatars/shadcn.jpg",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
export default function CustomersPage() {
|
|
||||||
return (
|
|
||||||
<div className="p-6">
|
|
||||||
<div className="bg-input p-4">Input</div>
|
|
||||||
<div className="bg-input/30 p-4">Input 50</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import { cookies } from "next/headers"
|
|
||||||
|
|
||||||
import {
|
|
||||||
SidebarInset,
|
|
||||||
SidebarProvider,
|
|
||||||
} from "@/registry/new-york-v4/ui/sidebar"
|
|
||||||
import { AppSidebar } from "@/app/(examples)/dashboard-03/components/app-sidebar"
|
|
||||||
import { SiteHeader } from "@/app/(examples)/dashboard-03/components/site-header"
|
|
||||||
|
|
||||||
import "../../themes.css"
|
|
||||||
|
|
||||||
export default async function DashboardLayout({
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
children: React.ReactNode
|
|
||||||
}) {
|
|
||||||
const cookieStore = await cookies()
|
|
||||||
const defaultOpen = cookieStore.get("sidebar_state")?.value === "true"
|
|
||||||
|
|
||||||
return (
|
|
||||||
<main className="[--header-height:calc(theme(spacing.14))]">
|
|
||||||
<SidebarProvider defaultOpen={defaultOpen} className="flex flex-col">
|
|
||||||
<SiteHeader />
|
|
||||||
<div className="flex flex-1">
|
|
||||||
<AppSidebar />
|
|
||||||
<SidebarInset>{children}</SidebarInset>
|
|
||||||
</div>
|
|
||||||
</SidebarProvider>
|
|
||||||
</main>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,206 +0,0 @@
|
|||||||
import { Metadata } from "next"
|
|
||||||
import {
|
|
||||||
DownloadIcon,
|
|
||||||
FilterIcon,
|
|
||||||
TrendingDownIcon,
|
|
||||||
TrendingUpIcon,
|
|
||||||
} from "lucide-react"
|
|
||||||
|
|
||||||
import { Badge } from "@/registry/new-york-v4/ui/badge"
|
|
||||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardDescription,
|
|
||||||
CardFooter,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from "@/registry/new-york-v4/ui/card"
|
|
||||||
import {
|
|
||||||
Tabs,
|
|
||||||
TabsContent,
|
|
||||||
TabsList,
|
|
||||||
TabsTrigger,
|
|
||||||
} from "@/registry/new-york-v4/ui/tabs"
|
|
||||||
import { AnalyticsDatePicker } from "@/app/(examples)/dashboard-03/components/analytics-date-picker"
|
|
||||||
import { ChartRevenue } from "@/app/(examples)/dashboard-03/components/chart-revenue"
|
|
||||||
import { ChartVisitors } from "@/app/(examples)/dashboard-03/components/chart-visitors"
|
|
||||||
import { ProductsTable } from "@/app/(examples)/dashboard-03/components/products-table"
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
|
||||||
title: "Dashboard",
|
|
||||||
description: "An example dashboard to test the new components.",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load from database.
|
|
||||||
const products = [
|
|
||||||
{
|
|
||||||
id: "1",
|
|
||||||
name: "BJÖRKSNÄS Dining Table",
|
|
||||||
price: 599.99,
|
|
||||||
stock: 12,
|
|
||||||
dateAdded: "2023-06-15",
|
|
||||||
status: "In Stock",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "2",
|
|
||||||
name: "POÄNG Armchair",
|
|
||||||
price: 249.99,
|
|
||||||
stock: 28,
|
|
||||||
dateAdded: "2023-07-22",
|
|
||||||
status: "In Stock",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "3",
|
|
||||||
name: "MALM Bed Frame",
|
|
||||||
price: 399.99,
|
|
||||||
stock: 15,
|
|
||||||
dateAdded: "2023-08-05",
|
|
||||||
status: "In Stock",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "4",
|
|
||||||
name: "KALLAX Shelf Unit",
|
|
||||||
price: 179.99,
|
|
||||||
stock: 32,
|
|
||||||
dateAdded: "2023-09-12",
|
|
||||||
status: "In Stock",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "5",
|
|
||||||
name: "STOCKHOLM Rug",
|
|
||||||
price: 299.99,
|
|
||||||
stock: 8,
|
|
||||||
dateAdded: "2023-10-18",
|
|
||||||
status: "Low Stock",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "6",
|
|
||||||
name: "KIVIK Sofa",
|
|
||||||
price: 899.99,
|
|
||||||
stock: 6,
|
|
||||||
dateAdded: "2023-11-02",
|
|
||||||
status: "Low Stock",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "7",
|
|
||||||
name: "LISABO Coffee Table",
|
|
||||||
price: 149.99,
|
|
||||||
stock: 22,
|
|
||||||
dateAdded: "2023-11-29",
|
|
||||||
status: "In Stock",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "8",
|
|
||||||
name: "HEMNES Bookcase",
|
|
||||||
price: 249.99,
|
|
||||||
stock: 17,
|
|
||||||
dateAdded: "2023-12-10",
|
|
||||||
status: "In Stock",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "9",
|
|
||||||
name: "EKEDALEN Dining Chairs (Set of 2)",
|
|
||||||
price: 199.99,
|
|
||||||
stock: 14,
|
|
||||||
dateAdded: "2024-01-05",
|
|
||||||
status: "In Stock",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "10",
|
|
||||||
name: "FRIHETEN Sleeper Sofa",
|
|
||||||
price: 799.99,
|
|
||||||
stock: 9,
|
|
||||||
dateAdded: "2024-01-18",
|
|
||||||
status: "Low Stock",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
export default function DashboardPage() {
|
|
||||||
return (
|
|
||||||
<div className="@container/page flex flex-1 flex-col gap-8 p-6">
|
|
||||||
<Tabs defaultValue="overview" className="gap-6">
|
|
||||||
<div
|
|
||||||
data-slot="dashboard-header"
|
|
||||||
className="flex items-center justify-between"
|
|
||||||
>
|
|
||||||
<TabsList className="w-full @3xl/page:w-fit">
|
|
||||||
<TabsTrigger value="overview">Overview</TabsTrigger>
|
|
||||||
<TabsTrigger value="analytics">Analytics</TabsTrigger>
|
|
||||||
<TabsTrigger value="reports">Reports</TabsTrigger>
|
|
||||||
<TabsTrigger value="exports" disabled>
|
|
||||||
Exports
|
|
||||||
</TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
<div className="hidden items-center gap-2 @3xl/page:flex">
|
|
||||||
<AnalyticsDatePicker />
|
|
||||||
<Button variant="outline">
|
|
||||||
<FilterIcon />
|
|
||||||
Filter
|
|
||||||
</Button>
|
|
||||||
<Button variant="outline">
|
|
||||||
<DownloadIcon />
|
|
||||||
Export
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<TabsContent value="overview" className="flex flex-col gap-4">
|
|
||||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4">
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Total Revenue</CardTitle>
|
|
||||||
<CardDescription>$1,250.00 in the last 30 days</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardFooter>
|
|
||||||
<Badge variant="outline">
|
|
||||||
<TrendingUpIcon />
|
|
||||||
+12.5%
|
|
||||||
</Badge>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>New Customers</CardTitle>
|
|
||||||
<CardDescription>-12 customers from last month</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardFooter>
|
|
||||||
<Badge variant="outline">
|
|
||||||
<TrendingDownIcon />
|
|
||||||
-20%
|
|
||||||
</Badge>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Active Accounts</CardTitle>
|
|
||||||
<CardDescription>+2,345 users from last month</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardFooter>
|
|
||||||
<Badge variant="outline">
|
|
||||||
<TrendingUpIcon />
|
|
||||||
+12.5%
|
|
||||||
</Badge>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Growth Rate</CardTitle>
|
|
||||||
<CardDescription>+12.5% increase per month</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardFooter>
|
|
||||||
<Badge variant="outline">
|
|
||||||
<TrendingUpIcon />
|
|
||||||
+4.5%
|
|
||||||
</Badge>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-1 gap-4 @4xl/page:grid-cols-[2fr_1fr]">
|
|
||||||
<ChartRevenue />
|
|
||||||
<ChartVisitors />
|
|
||||||
</div>
|
|
||||||
<ProductsTable products={products} />
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,500 +0,0 @@
|
|||||||
import { Metadata } from "next"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardAction,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardFooter,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from "@/registry/new-york-v4/ui/card"
|
|
||||||
import { Checkbox } from "@/registry/new-york-v4/ui/checkbox"
|
|
||||||
import { Input } from "@/registry/new-york-v4/ui/input"
|
|
||||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectGroup,
|
|
||||||
SelectItem,
|
|
||||||
SelectLabel,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@/registry/new-york-v4/ui/select"
|
|
||||||
import { Switch } from "@/registry/new-york-v4/ui/switch"
|
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
} from "@/registry/new-york-v4/ui/table"
|
|
||||||
import {
|
|
||||||
Tabs,
|
|
||||||
TabsContent,
|
|
||||||
TabsList,
|
|
||||||
TabsTrigger,
|
|
||||||
} from "@/registry/new-york-v4/ui/tabs"
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
|
||||||
title: "Settings",
|
|
||||||
description: "Manage your account settings",
|
|
||||||
}
|
|
||||||
|
|
||||||
const timezones = [
|
|
||||||
{
|
|
||||||
label: "Americas",
|
|
||||||
timezones: [
|
|
||||||
{ value: "America/New_York", label: "(GMT-5) New York" },
|
|
||||||
{ value: "America/Los_Angeles", label: "(GMT-8) Los Angeles" },
|
|
||||||
{ value: "America/Chicago", label: "(GMT-6) Chicago" },
|
|
||||||
{ value: "America/Toronto", label: "(GMT-5) Toronto" },
|
|
||||||
{ value: "America/Vancouver", label: "(GMT-8) Vancouver" },
|
|
||||||
{ value: "America/Sao_Paulo", label: "(GMT-3) São Paulo" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Europe",
|
|
||||||
timezones: [
|
|
||||||
{ value: "Europe/London", label: "(GMT+0) London" },
|
|
||||||
{ value: "Europe/Paris", label: "(GMT+1) Paris" },
|
|
||||||
{ value: "Europe/Berlin", label: "(GMT+1) Berlin" },
|
|
||||||
{ value: "Europe/Rome", label: "(GMT+1) Rome" },
|
|
||||||
{ value: "Europe/Madrid", label: "(GMT+1) Madrid" },
|
|
||||||
{ value: "Europe/Amsterdam", label: "(GMT+1) Amsterdam" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Asia/Pacific",
|
|
||||||
timezones: [
|
|
||||||
{ value: "Asia/Tokyo", label: "(GMT+9) Tokyo" },
|
|
||||||
{ value: "Asia/Shanghai", label: "(GMT+8) Shanghai" },
|
|
||||||
{ value: "Asia/Singapore", label: "(GMT+8) Singapore" },
|
|
||||||
{ value: "Asia/Dubai", label: "(GMT+4) Dubai" },
|
|
||||||
{ value: "Australia/Sydney", label: "(GMT+11) Sydney" },
|
|
||||||
{ value: "Asia/Seoul", label: "(GMT+9) Seoul" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
] as const
|
|
||||||
|
|
||||||
const loginHistory = [
|
|
||||||
{
|
|
||||||
date: "2024-01-01",
|
|
||||||
ip: "192.168.1.1",
|
|
||||||
location: "New York, USA",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
date: "2023-12-29",
|
|
||||||
ip: "172.16.0.100",
|
|
||||||
location: "London, UK",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
date: "2023-12-28",
|
|
||||||
ip: "10.0.0.50",
|
|
||||||
location: "Toronto, Canada",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
date: "2023-12-25",
|
|
||||||
ip: "192.168.2.15",
|
|
||||||
location: "Sydney, Australia",
|
|
||||||
},
|
|
||||||
] as const
|
|
||||||
|
|
||||||
const activeSessions = [
|
|
||||||
{
|
|
||||||
device: "MacBook Pro",
|
|
||||||
browser: "Chrome",
|
|
||||||
os: "macOS",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
device: "iPhone",
|
|
||||||
browser: "Safari",
|
|
||||||
os: "iOS",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
device: "iPad",
|
|
||||||
browser: "Safari",
|
|
||||||
os: "iOS",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
device: "Android Phone",
|
|
||||||
browser: "Chrome",
|
|
||||||
os: "Android",
|
|
||||||
},
|
|
||||||
] as const
|
|
||||||
|
|
||||||
export default function SettingsPage() {
|
|
||||||
return (
|
|
||||||
<div className="@container/page flex flex-1 flex-col gap-8 p-6">
|
|
||||||
<Tabs defaultValue="account" className="gap-6">
|
|
||||||
<div
|
|
||||||
data-slot="dashboard-header"
|
|
||||||
className="flex items-center justify-between"
|
|
||||||
>
|
|
||||||
<TabsList>
|
|
||||||
<TabsTrigger value="account">Account</TabsTrigger>
|
|
||||||
<TabsTrigger value="security">Security</TabsTrigger>
|
|
||||||
<TabsTrigger value="notifications">Notifications</TabsTrigger>
|
|
||||||
<TabsTrigger value="privacy">Privacy</TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
</div>
|
|
||||||
<TabsContent value="account" className="grid gap-6">
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Account Settings</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Make changes to your account here.
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<form id="form-account" className="@container">
|
|
||||||
<FieldGroup>
|
|
||||||
<Field>
|
|
||||||
<Label htmlFor="name">Name</Label>
|
|
||||||
<FieldControl>
|
|
||||||
<Input
|
|
||||||
id="name"
|
|
||||||
placeholder="First and last name"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</FieldControl>
|
|
||||||
<FieldDescription>
|
|
||||||
This is your public display name.
|
|
||||||
</FieldDescription>
|
|
||||||
</Field>
|
|
||||||
<Field>
|
|
||||||
<Label htmlFor="email">Email</Label>
|
|
||||||
<FieldControl>
|
|
||||||
<Input
|
|
||||||
id="email"
|
|
||||||
placeholder="you@example.com"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</FieldControl>
|
|
||||||
</Field>
|
|
||||||
<Field>
|
|
||||||
<Label htmlFor="timezone">Timezone</Label>
|
|
||||||
<FieldControl>
|
|
||||||
<Select>
|
|
||||||
<SelectTrigger id="timezone">
|
|
||||||
<SelectValue placeholder="Select a timezone" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{timezones.map((timezone) => (
|
|
||||||
<SelectGroup key={timezone.label}>
|
|
||||||
<SelectLabel>{timezone.label}</SelectLabel>
|
|
||||||
{timezone.timezones.map((time) => (
|
|
||||||
<SelectItem key={time.value} value={time.value}>
|
|
||||||
{time.label}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectGroup>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</FieldControl>
|
|
||||||
</Field>
|
|
||||||
</FieldGroup>
|
|
||||||
</form>
|
|
||||||
</CardContent>
|
|
||||||
<CardFooter className="border-t">
|
|
||||||
<Button type="submit" form="form-account" variant="secondary">
|
|
||||||
Save changes
|
|
||||||
</Button>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Notifications</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Manage how you receive notifications.
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<form id="form-notifications" className="@container">
|
|
||||||
<FieldGroup>
|
|
||||||
<Field>
|
|
||||||
<Label htmlFor="channels">Notification Channels</Label>
|
|
||||||
<FieldControl className="flex flex-col gap-2">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Checkbox id="notification-email" />
|
|
||||||
<Label htmlFor="notification-email">Email</Label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Checkbox id="notification-sms" />
|
|
||||||
<Label htmlFor="notification-sms">SMS</Label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Checkbox id="notification-push" />
|
|
||||||
<Label htmlFor="notification-push">Push</Label>
|
|
||||||
</div>
|
|
||||||
</FieldControl>
|
|
||||||
<FieldDescription>
|
|
||||||
Choose how you want to receive notifications.
|
|
||||||
</FieldDescription>
|
|
||||||
</Field>
|
|
||||||
<Field>
|
|
||||||
<Label htmlFor="types">Notification Types</Label>
|
|
||||||
<FieldControl className="flex flex-col gap-2">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Checkbox id="notification-account" />
|
|
||||||
<Label htmlFor="notification-account">
|
|
||||||
Account Activity
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Checkbox
|
|
||||||
id="notification-security"
|
|
||||||
defaultChecked
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
<Label htmlFor="notification-security">
|
|
||||||
Security Alerts
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Checkbox id="notification-marketing" />
|
|
||||||
<Label htmlFor="notification-marketing">
|
|
||||||
Marketing & Promotions
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
</FieldControl>
|
|
||||||
<FieldDescription>
|
|
||||||
Choose how you want to receive notifications.
|
|
||||||
</FieldDescription>
|
|
||||||
</Field>
|
|
||||||
</FieldGroup>
|
|
||||||
</form>
|
|
||||||
</CardContent>
|
|
||||||
<CardFooter className="border-t">
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
form="form-notifications"
|
|
||||||
variant="secondary"
|
|
||||||
>
|
|
||||||
Save changes
|
|
||||||
</Button>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
</TabsContent>
|
|
||||||
<TabsContent
|
|
||||||
value="security"
|
|
||||||
className="grid gap-6 @3xl/page:grid-cols-2"
|
|
||||||
>
|
|
||||||
<Card className="@3xl/page:col-span-2">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Security Settings</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Make changes to your security settings here.
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="@container">
|
|
||||||
<form id="form-security">
|
|
||||||
<FieldGroup>
|
|
||||||
<Field>
|
|
||||||
<Label htmlFor="current-password">Current Password</Label>
|
|
||||||
<FieldControl>
|
|
||||||
<Input
|
|
||||||
id="current-password"
|
|
||||||
placeholder="Current password"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</FieldControl>
|
|
||||||
<FieldDescription>
|
|
||||||
This is your current password.
|
|
||||||
</FieldDescription>
|
|
||||||
</Field>
|
|
||||||
<Field>
|
|
||||||
<Label htmlFor="new-password">New Password</Label>
|
|
||||||
<FieldControl>
|
|
||||||
<Input
|
|
||||||
id="new-password"
|
|
||||||
placeholder="New password"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</FieldControl>
|
|
||||||
</Field>
|
|
||||||
<Field>
|
|
||||||
<Label htmlFor="confirm-password">Confirm Password</Label>
|
|
||||||
<FieldControl>
|
|
||||||
<Input
|
|
||||||
id="confirm-password"
|
|
||||||
placeholder="Confirm password"
|
|
||||||
/>
|
|
||||||
</FieldControl>
|
|
||||||
</Field>
|
|
||||||
<Field>
|
|
||||||
<FieldControl>
|
|
||||||
<Switch
|
|
||||||
id="enable-two-factor-auth"
|
|
||||||
className="self-start"
|
|
||||||
/>
|
|
||||||
</FieldControl>
|
|
||||||
<Label htmlFor="enable-two-factor-auth">
|
|
||||||
Enable two-factor authentication
|
|
||||||
</Label>
|
|
||||||
<FieldDescription>
|
|
||||||
This will add an extra layer of security to your account.
|
|
||||||
Make this an extra long description to test the layout.
|
|
||||||
</FieldDescription>
|
|
||||||
</Field>
|
|
||||||
</FieldGroup>
|
|
||||||
</form>
|
|
||||||
</CardContent>
|
|
||||||
<CardFooter className="border-t">
|
|
||||||
<Button type="submit" form="form-security" variant="secondary">
|
|
||||||
Save changes
|
|
||||||
</Button>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Login History</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Recent login activities on your account.
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<Table>
|
|
||||||
<TableHeader>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead>Date</TableHead>
|
|
||||||
<TableHead className="hidden @md/page:table-cell">
|
|
||||||
IP
|
|
||||||
</TableHead>
|
|
||||||
<TableHead>Location</TableHead>
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{loginHistory.map((login) => (
|
|
||||||
<TableRow key={login.date}>
|
|
||||||
<TableCell>
|
|
||||||
<div className="flex flex-col gap-1">
|
|
||||||
{new Date(login.date).toLocaleDateString("en-US", {
|
|
||||||
year: "numeric",
|
|
||||||
month: "long",
|
|
||||||
day: "numeric",
|
|
||||||
})}
|
|
||||||
<span className="flex @md/page:hidden">
|
|
||||||
{login.ip}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="hidden @md/page:table-cell">
|
|
||||||
{login.ip}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>{login.location}</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Active Sessions</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Current active sessions on your account.
|
|
||||||
</CardDescription>
|
|
||||||
<CardAction>
|
|
||||||
<Button variant="outline" size="sm">
|
|
||||||
<span className="hidden @md/card-header:block">
|
|
||||||
Manage Sessions
|
|
||||||
</span>
|
|
||||||
<span className="block @md/card-header:hidden">Manage</span>
|
|
||||||
</Button>
|
|
||||||
</CardAction>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<Table>
|
|
||||||
<TableHeader>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead>Device</TableHead>
|
|
||||||
<TableHead>Browser</TableHead>
|
|
||||||
<TableHead>OS</TableHead>
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{activeSessions.map((session) => (
|
|
||||||
<TableRow key={session.device}>
|
|
||||||
<TableCell>{session.device}</TableCell>
|
|
||||||
<TableCell>{session.browser}</TableCell>
|
|
||||||
<TableCell>{session.os}</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function FieldGroup({ children }: React.ComponentProps<"div">) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-slot="field-group"
|
|
||||||
className="@container/field-group flex max-w-4xl min-w-0 flex-col gap-8 @3xl:gap-6"
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function Field({ children, className, ...props }: React.ComponentProps<"div">) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-slot="field"
|
|
||||||
className={cn(
|
|
||||||
"grid auto-rows-min items-start gap-3 *:data-[slot=label]:col-start-1 *:data-[slot=label]:row-start-1 @3xl/field-group:grid-cols-2 @3xl/field-group:gap-6",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function FieldControl({
|
|
||||||
children,
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<"div">) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-slot="field-control"
|
|
||||||
className={cn(
|
|
||||||
"@3xl/field-group:col-start-2 @3xl/field-group:row-span-2 @3xl/field-group:row-start-1 @3xl/field-group:self-start",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function FieldDescription({
|
|
||||||
children,
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<"p">) {
|
|
||||||
return (
|
|
||||||
<p
|
|
||||||
data-slot="field-description"
|
|
||||||
className={cn(
|
|
||||||
"text-muted-foreground text-sm @3xl/field-group:col-start-1 @3xl/field-group:row-start-1 @3xl/field-group:translate-y-6",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</p>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import {
|
|
||||||
IconCamera,
|
|
||||||
IconChartBar,
|
|
||||||
IconDashboard,
|
|
||||||
IconDatabase,
|
|
||||||
IconFileAi,
|
|
||||||
IconFileDescription,
|
|
||||||
IconFileWord,
|
|
||||||
IconFolder,
|
|
||||||
IconHelp,
|
|
||||||
IconInnerShadowTop,
|
|
||||||
IconListDetails,
|
|
||||||
IconReport,
|
|
||||||
IconSearch,
|
|
||||||
IconSettings,
|
|
||||||
IconUsers,
|
|
||||||
} from "@tabler/icons-react"
|
|
||||||
|
|
||||||
import {
|
|
||||||
Sidebar,
|
|
||||||
SidebarContent,
|
|
||||||
SidebarFooter,
|
|
||||||
SidebarHeader,
|
|
||||||
SidebarMenu,
|
|
||||||
SidebarMenuButton,
|
|
||||||
SidebarMenuItem,
|
|
||||||
} from "@/registry/new-york-v4/ui/sidebar"
|
|
||||||
import { NavDocuments } from "@/app/(examples)/dashboard/components/nav-documents"
|
|
||||||
import { NavMain } from "@/app/(examples)/dashboard/components/nav-main"
|
|
||||||
import { NavSecondary } from "@/app/(examples)/dashboard/components/nav-secondary"
|
|
||||||
import { NavUser } from "@/app/(examples)/dashboard/components/nav-user"
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
user: {
|
|
||||||
name: "shadcn",
|
|
||||||
email: "m@example.com",
|
|
||||||
avatar: "/avatars/shadcn.jpg",
|
|
||||||
},
|
|
||||||
navMain: [
|
|
||||||
{
|
|
||||||
title: "Dashboard",
|
|
||||||
url: "#",
|
|
||||||
icon: IconDashboard,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Lifecycle",
|
|
||||||
url: "#",
|
|
||||||
icon: IconListDetails,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Analytics",
|
|
||||||
url: "#",
|
|
||||||
icon: IconChartBar,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Projects",
|
|
||||||
url: "#",
|
|
||||||
icon: IconFolder,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Team",
|
|
||||||
url: "#",
|
|
||||||
icon: IconUsers,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
navClouds: [
|
|
||||||
{
|
|
||||||
title: "Capture",
|
|
||||||
icon: IconCamera,
|
|
||||||
isActive: true,
|
|
||||||
url: "#",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
title: "Active Proposals",
|
|
||||||
url: "#",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Archived",
|
|
||||||
url: "#",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Proposal",
|
|
||||||
icon: IconFileDescription,
|
|
||||||
url: "#",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
title: "Active Proposals",
|
|
||||||
url: "#",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Archived",
|
|
||||||
url: "#",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Prompts",
|
|
||||||
icon: IconFileAi,
|
|
||||||
url: "#",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
title: "Active Proposals",
|
|
||||||
url: "#",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Archived",
|
|
||||||
url: "#",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
navSecondary: [
|
|
||||||
{
|
|
||||||
title: "Settings",
|
|
||||||
url: "#",
|
|
||||||
icon: IconSettings,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Get Help",
|
|
||||||
url: "#",
|
|
||||||
icon: IconHelp,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Search",
|
|
||||||
url: "#",
|
|
||||||
icon: IconSearch,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
documents: [
|
|
||||||
{
|
|
||||||
name: "Data Library",
|
|
||||||
url: "#",
|
|
||||||
icon: IconDatabase,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Reports",
|
|
||||||
url: "#",
|
|
||||||
icon: IconReport,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Word Assistant",
|
|
||||||
url: "#",
|
|
||||||
icon: IconFileWord,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
|
||||||
return (
|
|
||||||
<Sidebar collapsible="offcanvas" {...props}>
|
|
||||||
<SidebarHeader>
|
|
||||||
<SidebarMenu>
|
|
||||||
<SidebarMenuItem>
|
|
||||||
<SidebarMenuButton
|
|
||||||
asChild
|
|
||||||
className="data-[slot=sidebar-menu-button]:!p-1.5"
|
|
||||||
>
|
|
||||||
<a href="#">
|
|
||||||
<IconInnerShadowTop className="!size-5" />
|
|
||||||
<span className="text-base font-semibold">Acme Inc.</span>
|
|
||||||
</a>
|
|
||||||
</SidebarMenuButton>
|
|
||||||
</SidebarMenuItem>
|
|
||||||
</SidebarMenu>
|
|
||||||
</SidebarHeader>
|
|
||||||
<SidebarContent>
|
|
||||||
<NavMain items={data.navMain} />
|
|
||||||
<NavDocuments items={data.documents} />
|
|
||||||
<NavSecondary items={data.navSecondary} className="mt-auto" />
|
|
||||||
</SidebarContent>
|
|
||||||
<SidebarFooter>
|
|
||||||
<NavUser user={data.user} />
|
|
||||||
</SidebarFooter>
|
|
||||||
</Sidebar>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,292 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"
|
|
||||||
|
|
||||||
import { useIsMobile } from "@/registry/new-york-v4/hooks/use-mobile"
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardAction,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from "@/registry/new-york-v4/ui/card"
|
|
||||||
import {
|
|
||||||
ChartConfig,
|
|
||||||
ChartContainer,
|
|
||||||
ChartTooltip,
|
|
||||||
ChartTooltipContent,
|
|
||||||
} from "@/registry/new-york-v4/ui/chart"
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@/registry/new-york-v4/ui/select"
|
|
||||||
import {
|
|
||||||
ToggleGroup,
|
|
||||||
ToggleGroupItem,
|
|
||||||
} from "@/registry/new-york-v4/ui/toggle-group"
|
|
||||||
|
|
||||||
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: "var(--primary)",
|
|
||||||
},
|
|
||||||
mobile: {
|
|
||||||
label: "Mobile",
|
|
||||||
color: "var(--primary)",
|
|
||||||
},
|
|
||||||
} satisfies ChartConfig
|
|
||||||
|
|
||||||
export function ChartAreaInteractive() {
|
|
||||||
const isMobile = useIsMobile()
|
|
||||||
const [timeRange, setTimeRange] = React.useState("90d")
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (isMobile) {
|
|
||||||
setTimeRange("7d")
|
|
||||||
}
|
|
||||||
}, [isMobile])
|
|
||||||
|
|
||||||
const filteredData = chartData.filter((item) => {
|
|
||||||
const date = new Date(item.date)
|
|
||||||
const referenceDate = new Date("2024-06-30")
|
|
||||||
let daysToSubtract = 90
|
|
||||||
if (timeRange === "30d") {
|
|
||||||
daysToSubtract = 30
|
|
||||||
} else if (timeRange === "7d") {
|
|
||||||
daysToSubtract = 7
|
|
||||||
}
|
|
||||||
const startDate = new Date(referenceDate)
|
|
||||||
startDate.setDate(startDate.getDate() - daysToSubtract)
|
|
||||||
return date >= startDate
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card className="@container/card">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Total Visitors</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
<span className="hidden @[540px]/card:block">
|
|
||||||
Total for the last 3 months
|
|
||||||
</span>
|
|
||||||
<span className="@[540px]/card:hidden">Last 3 months</span>
|
|
||||||
</CardDescription>
|
|
||||||
<CardAction>
|
|
||||||
<ToggleGroup
|
|
||||||
type="single"
|
|
||||||
value={timeRange}
|
|
||||||
onValueChange={setTimeRange}
|
|
||||||
variant="outline"
|
|
||||||
className="hidden *:data-[slot=toggle-group-item]:!px-4 @[767px]/card:flex"
|
|
||||||
>
|
|
||||||
<ToggleGroupItem value="90d">Last 3 months</ToggleGroupItem>
|
|
||||||
<ToggleGroupItem value="30d">Last 30 days</ToggleGroupItem>
|
|
||||||
<ToggleGroupItem value="7d">Last 7 days</ToggleGroupItem>
|
|
||||||
</ToggleGroup>
|
|
||||||
<Select value={timeRange} onValueChange={setTimeRange}>
|
|
||||||
<SelectTrigger
|
|
||||||
className="flex w-40 **:data-[slot=select-value]:block **:data-[slot=select-value]:truncate @[767px]/card:hidden"
|
|
||||||
size="sm"
|
|
||||||
aria-label="Select a value"
|
|
||||||
>
|
|
||||||
<SelectValue placeholder="Last 3 months" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent className="rounded-xl">
|
|
||||||
<SelectItem value="90d" className="rounded-lg">
|
|
||||||
Last 3 months
|
|
||||||
</SelectItem>
|
|
||||||
<SelectItem value="30d" className="rounded-lg">
|
|
||||||
Last 30 days
|
|
||||||
</SelectItem>
|
|
||||||
<SelectItem value="7d" className="rounded-lg">
|
|
||||||
Last 7 days
|
|
||||||
</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</CardAction>
|
|
||||||
</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={1.0}
|
|
||||||
/>
|
|
||||||
<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}
|
|
||||||
defaultIndex={isMobile ? -1 : 10}
|
|
||||||
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"
|
|
||||||
/>
|
|
||||||
</AreaChart>
|
|
||||||
</ChartContainer>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,807 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import {
|
|
||||||
DndContext,
|
|
||||||
KeyboardSensor,
|
|
||||||
MouseSensor,
|
|
||||||
TouchSensor,
|
|
||||||
closestCenter,
|
|
||||||
useSensor,
|
|
||||||
useSensors,
|
|
||||||
type DragEndEvent,
|
|
||||||
type UniqueIdentifier,
|
|
||||||
} from "@dnd-kit/core"
|
|
||||||
import { restrictToVerticalAxis } from "@dnd-kit/modifiers"
|
|
||||||
import {
|
|
||||||
SortableContext,
|
|
||||||
arrayMove,
|
|
||||||
useSortable,
|
|
||||||
verticalListSortingStrategy,
|
|
||||||
} from "@dnd-kit/sortable"
|
|
||||||
import { CSS } from "@dnd-kit/utilities"
|
|
||||||
import {
|
|
||||||
IconChevronDown,
|
|
||||||
IconChevronLeft,
|
|
||||||
IconChevronRight,
|
|
||||||
IconChevronsLeft,
|
|
||||||
IconChevronsRight,
|
|
||||||
IconCircleCheckFilled,
|
|
||||||
IconDotsVertical,
|
|
||||||
IconGripVertical,
|
|
||||||
IconLayoutColumns,
|
|
||||||
IconLoader,
|
|
||||||
IconPlus,
|
|
||||||
IconTrendingUp,
|
|
||||||
} from "@tabler/icons-react"
|
|
||||||
import {
|
|
||||||
ColumnDef,
|
|
||||||
ColumnFiltersState,
|
|
||||||
Row,
|
|
||||||
SortingState,
|
|
||||||
VisibilityState,
|
|
||||||
flexRender,
|
|
||||||
getCoreRowModel,
|
|
||||||
getFacetedRowModel,
|
|
||||||
getFacetedUniqueValues,
|
|
||||||
getFilteredRowModel,
|
|
||||||
getPaginationRowModel,
|
|
||||||
getSortedRowModel,
|
|
||||||
useReactTable,
|
|
||||||
} from "@tanstack/react-table"
|
|
||||||
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"
|
|
||||||
import { toast } from "sonner"
|
|
||||||
import { z } from "zod"
|
|
||||||
|
|
||||||
import { useIsMobile } from "@/registry/new-york-v4/hooks/use-mobile"
|
|
||||||
import { Badge } from "@/registry/new-york-v4/ui/badge"
|
|
||||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
|
||||||
import {
|
|
||||||
ChartConfig,
|
|
||||||
ChartContainer,
|
|
||||||
ChartTooltip,
|
|
||||||
ChartTooltipContent,
|
|
||||||
} from "@/registry/new-york-v4/ui/chart"
|
|
||||||
import { Checkbox } from "@/registry/new-york-v4/ui/checkbox"
|
|
||||||
import {
|
|
||||||
Drawer,
|
|
||||||
DrawerClose,
|
|
||||||
DrawerContent,
|
|
||||||
DrawerDescription,
|
|
||||||
DrawerFooter,
|
|
||||||
DrawerHeader,
|
|
||||||
DrawerTitle,
|
|
||||||
DrawerTrigger,
|
|
||||||
} from "@/registry/new-york-v4/ui/drawer"
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuCheckboxItem,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuSeparator,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "@/registry/new-york-v4/ui/dropdown-menu"
|
|
||||||
import { Input } from "@/registry/new-york-v4/ui/input"
|
|
||||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@/registry/new-york-v4/ui/select"
|
|
||||||
import { Separator } from "@/registry/new-york-v4/ui/separator"
|
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
} from "@/registry/new-york-v4/ui/table"
|
|
||||||
import {
|
|
||||||
Tabs,
|
|
||||||
TabsContent,
|
|
||||||
TabsList,
|
|
||||||
TabsTrigger,
|
|
||||||
} from "@/registry/new-york-v4/ui/tabs"
|
|
||||||
|
|
||||||
export const schema = z.object({
|
|
||||||
id: z.number(),
|
|
||||||
header: z.string(),
|
|
||||||
type: z.string(),
|
|
||||||
status: z.string(),
|
|
||||||
target: z.string(),
|
|
||||||
limit: z.string(),
|
|
||||||
reviewer: z.string(),
|
|
||||||
})
|
|
||||||
|
|
||||||
// Create a separate component for the drag handle
|
|
||||||
function DragHandle({ id }: { id: number }) {
|
|
||||||
const { attributes, listeners } = useSortable({
|
|
||||||
id,
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
{...attributes}
|
|
||||||
{...listeners}
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="text-muted-foreground size-7 hover:bg-transparent"
|
|
||||||
>
|
|
||||||
<IconGripVertical className="text-muted-foreground size-3" />
|
|
||||||
<span className="sr-only">Drag to reorder</span>
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const columns: ColumnDef<z.infer<typeof schema>>[] = [
|
|
||||||
{
|
|
||||||
id: "drag",
|
|
||||||
header: () => null,
|
|
||||||
cell: ({ row }) => <DragHandle id={row.original.id} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "select",
|
|
||||||
header: ({ table }) => (
|
|
||||||
<div className="flex items-center justify-center">
|
|
||||||
<Checkbox
|
|
||||||
checked={
|
|
||||||
table.getIsAllPageRowsSelected() ||
|
|
||||||
(table.getIsSomePageRowsSelected() && "indeterminate")
|
|
||||||
}
|
|
||||||
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
|
||||||
aria-label="Select all"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<div className="flex items-center justify-center">
|
|
||||||
<Checkbox
|
|
||||||
checked={row.getIsSelected()}
|
|
||||||
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
|
||||||
aria-label="Select row"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
enableSorting: false,
|
|
||||||
enableHiding: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "header",
|
|
||||||
header: "Header",
|
|
||||||
cell: ({ row }) => {
|
|
||||||
return <TableCellViewer item={row.original} />
|
|
||||||
},
|
|
||||||
enableHiding: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "type",
|
|
||||||
header: "Section Type",
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<div className="w-32">
|
|
||||||
<Badge variant="outline" className="text-muted-foreground px-1.5">
|
|
||||||
{row.original.type}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "status",
|
|
||||||
header: "Status",
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<Badge variant="outline" className="text-muted-foreground px-1.5">
|
|
||||||
{row.original.status === "Done" ? (
|
|
||||||
<IconCircleCheckFilled className="fill-green-500 dark:fill-green-400" />
|
|
||||||
) : (
|
|
||||||
<IconLoader />
|
|
||||||
)}
|
|
||||||
{row.original.status}
|
|
||||||
</Badge>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "target",
|
|
||||||
header: () => <div className="w-full text-right">Target</div>,
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<form
|
|
||||||
onSubmit={(e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
toast.promise(new Promise((resolve) => setTimeout(resolve, 1000)), {
|
|
||||||
loading: `Saving ${row.original.header}`,
|
|
||||||
success: "Done",
|
|
||||||
error: "Error",
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Label htmlFor={`${row.original.id}-target`} className="sr-only">
|
|
||||||
Target
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
className="hover:bg-input/30 focus-visible:bg-background dark:hover:bg-input/30 dark:focus-visible:bg-input/30 h-8 w-16 border-transparent bg-transparent text-right shadow-none focus-visible:border dark:bg-transparent"
|
|
||||||
defaultValue={row.original.target}
|
|
||||||
id={`${row.original.id}-target`}
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "limit",
|
|
||||||
header: () => <div className="w-full text-right">Limit</div>,
|
|
||||||
cell: ({ row }) => (
|
|
||||||
<form
|
|
||||||
onSubmit={(e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
toast.promise(new Promise((resolve) => setTimeout(resolve, 1000)), {
|
|
||||||
loading: `Saving ${row.original.header}`,
|
|
||||||
success: "Done",
|
|
||||||
error: "Error",
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Label htmlFor={`${row.original.id}-limit`} className="sr-only">
|
|
||||||
Limit
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
className="hover:bg-input/30 focus-visible:bg-background dark:hover:bg-input/30 dark:focus-visible:bg-input/30 h-8 w-16 border-transparent bg-transparent text-right shadow-none focus-visible:border dark:bg-transparent"
|
|
||||||
defaultValue={row.original.limit}
|
|
||||||
id={`${row.original.id}-limit`}
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "reviewer",
|
|
||||||
header: "Reviewer",
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const isAssigned = row.original.reviewer !== "Assign reviewer"
|
|
||||||
|
|
||||||
if (isAssigned) {
|
|
||||||
return row.original.reviewer
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Label htmlFor={`${row.original.id}-reviewer`} className="sr-only">
|
|
||||||
Reviewer
|
|
||||||
</Label>
|
|
||||||
<Select>
|
|
||||||
<SelectTrigger
|
|
||||||
className="w-38 **:data-[slot=select-value]:block **:data-[slot=select-value]:truncate"
|
|
||||||
size="sm"
|
|
||||||
id={`${row.original.id}-reviewer`}
|
|
||||||
>
|
|
||||||
<SelectValue placeholder="Assign reviewer" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent align="end">
|
|
||||||
<SelectItem value="Eddie Lake">Eddie Lake</SelectItem>
|
|
||||||
<SelectItem value="Jamik Tashpulatov">
|
|
||||||
Jamik Tashpulatov
|
|
||||||
</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "actions",
|
|
||||||
cell: () => (
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
className="data-[state=open]:bg-muted text-muted-foreground flex size-8"
|
|
||||||
size="icon"
|
|
||||||
>
|
|
||||||
<IconDotsVertical />
|
|
||||||
<span className="sr-only">Open menu</span>
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align="end" className="w-32">
|
|
||||||
<DropdownMenuItem>Edit</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem>Make a copy</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem>Favorite</DropdownMenuItem>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<DropdownMenuItem variant="destructive">Delete</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
function DraggableRow({ row }: { row: Row<z.infer<typeof schema>> }) {
|
|
||||||
const { transform, transition, setNodeRef, isDragging } = useSortable({
|
|
||||||
id: row.original.id,
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TableRow
|
|
||||||
data-state={row.getIsSelected() && "selected"}
|
|
||||||
data-dragging={isDragging}
|
|
||||||
ref={setNodeRef}
|
|
||||||
className="relative z-0 data-[dragging=true]:z-10 data-[dragging=true]:opacity-80"
|
|
||||||
style={{
|
|
||||||
transform: CSS.Transform.toString(transform),
|
|
||||||
transition: transition,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{row.getVisibleCells().map((cell) => (
|
|
||||||
<TableCell key={cell.id}>
|
|
||||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
||||||
</TableCell>
|
|
||||||
))}
|
|
||||||
</TableRow>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function DataTable({
|
|
||||||
data: initialData,
|
|
||||||
}: {
|
|
||||||
data: z.infer<typeof schema>[]
|
|
||||||
}) {
|
|
||||||
const [data, setData] = React.useState(() => initialData)
|
|
||||||
const [rowSelection, setRowSelection] = React.useState({})
|
|
||||||
const [columnVisibility, setColumnVisibility] =
|
|
||||||
React.useState<VisibilityState>({})
|
|
||||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
const [sorting, setSorting] = React.useState<SortingState>([])
|
|
||||||
const [pagination, setPagination] = React.useState({
|
|
||||||
pageIndex: 0,
|
|
||||||
pageSize: 10,
|
|
||||||
})
|
|
||||||
const sortableId = React.useId()
|
|
||||||
const sensors = useSensors(
|
|
||||||
useSensor(MouseSensor, {}),
|
|
||||||
useSensor(TouchSensor, {}),
|
|
||||||
useSensor(KeyboardSensor, {})
|
|
||||||
)
|
|
||||||
|
|
||||||
const dataIds = React.useMemo<UniqueIdentifier[]>(
|
|
||||||
() => data?.map(({ id }) => id) || [],
|
|
||||||
[data]
|
|
||||||
)
|
|
||||||
|
|
||||||
const table = useReactTable({
|
|
||||||
data,
|
|
||||||
columns,
|
|
||||||
state: {
|
|
||||||
sorting,
|
|
||||||
columnVisibility,
|
|
||||||
rowSelection,
|
|
||||||
columnFilters,
|
|
||||||
pagination,
|
|
||||||
},
|
|
||||||
getRowId: (row) => row.id.toString(),
|
|
||||||
enableRowSelection: true,
|
|
||||||
onRowSelectionChange: setRowSelection,
|
|
||||||
onSortingChange: setSorting,
|
|
||||||
onColumnFiltersChange: setColumnFilters,
|
|
||||||
onColumnVisibilityChange: setColumnVisibility,
|
|
||||||
onPaginationChange: setPagination,
|
|
||||||
getCoreRowModel: getCoreRowModel(),
|
|
||||||
getFilteredRowModel: getFilteredRowModel(),
|
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
|
||||||
getSortedRowModel: getSortedRowModel(),
|
|
||||||
getFacetedRowModel: getFacetedRowModel(),
|
|
||||||
getFacetedUniqueValues: getFacetedUniqueValues(),
|
|
||||||
})
|
|
||||||
|
|
||||||
function handleDragEnd(event: DragEndEvent) {
|
|
||||||
const { active, over } = event
|
|
||||||
if (active && over && active.id !== over.id) {
|
|
||||||
setData((data) => {
|
|
||||||
const oldIndex = dataIds.indexOf(active.id)
|
|
||||||
const newIndex = dataIds.indexOf(over.id)
|
|
||||||
return arrayMove(data, oldIndex, newIndex)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tabs
|
|
||||||
defaultValue="outline"
|
|
||||||
className="w-full flex-col justify-start gap-6"
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-between px-4 lg:px-6">
|
|
||||||
<Label htmlFor="view-selector" className="sr-only">
|
|
||||||
View
|
|
||||||
</Label>
|
|
||||||
<Select defaultValue="outline">
|
|
||||||
<SelectTrigger
|
|
||||||
className="flex w-fit @4xl/main:hidden"
|
|
||||||
size="sm"
|
|
||||||
id="view-selector"
|
|
||||||
>
|
|
||||||
<SelectValue placeholder="Select a view" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="outline">Outline</SelectItem>
|
|
||||||
<SelectItem value="past-performance">Past Performance</SelectItem>
|
|
||||||
<SelectItem value="key-personnel">Key Personnel</SelectItem>
|
|
||||||
<SelectItem value="focus-documents">Focus Documents</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<TabsList className="**:data-[slot=badge]:bg-muted-foreground/30 hidden **:data-[slot=badge]:size-5 **:data-[slot=badge]:rounded-full **:data-[slot=badge]:px-1 @4xl/main:flex">
|
|
||||||
<TabsTrigger value="outline">Outline</TabsTrigger>
|
|
||||||
<TabsTrigger value="past-performance">
|
|
||||||
Past Performance <Badge variant="secondary">3</Badge>
|
|
||||||
</TabsTrigger>
|
|
||||||
<TabsTrigger value="key-personnel">
|
|
||||||
Key Personnel <Badge variant="secondary">2</Badge>
|
|
||||||
</TabsTrigger>
|
|
||||||
<TabsTrigger value="focus-documents">Focus Documents</TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button variant="outline" size="sm">
|
|
||||||
<IconLayoutColumns />
|
|
||||||
<span className="hidden lg:inline">Customize Columns</span>
|
|
||||||
<span className="lg:hidden">Columns</span>
|
|
||||||
<IconChevronDown />
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align="end" className="w-56">
|
|
||||||
{table
|
|
||||||
.getAllColumns()
|
|
||||||
.filter(
|
|
||||||
(column) =>
|
|
||||||
typeof column.accessorFn !== "undefined" &&
|
|
||||||
column.getCanHide()
|
|
||||||
)
|
|
||||||
.map((column) => {
|
|
||||||
return (
|
|
||||||
<DropdownMenuCheckboxItem
|
|
||||||
key={column.id}
|
|
||||||
className="capitalize"
|
|
||||||
checked={column.getIsVisible()}
|
|
||||||
onCheckedChange={(value) =>
|
|
||||||
column.toggleVisibility(!!value)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{column.id}
|
|
||||||
</DropdownMenuCheckboxItem>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
<Button variant="outline" size="sm">
|
|
||||||
<IconPlus />
|
|
||||||
<span className="hidden lg:inline">Add Section</span>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<TabsContent
|
|
||||||
value="outline"
|
|
||||||
className="relative flex flex-col gap-4 overflow-auto px-4 lg:px-6"
|
|
||||||
>
|
|
||||||
<div className="overflow-hidden rounded-lg border">
|
|
||||||
<DndContext
|
|
||||||
collisionDetection={closestCenter}
|
|
||||||
modifiers={[restrictToVerticalAxis]}
|
|
||||||
onDragEnd={handleDragEnd}
|
|
||||||
sensors={sensors}
|
|
||||||
id={sortableId}
|
|
||||||
>
|
|
||||||
<Table>
|
|
||||||
<TableHeader className="bg-muted sticky top-0 z-10">
|
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
|
||||||
<TableRow key={headerGroup.id}>
|
|
||||||
{headerGroup.headers.map((header) => {
|
|
||||||
return (
|
|
||||||
<TableHead key={header.id} colSpan={header.colSpan}>
|
|
||||||
{header.isPlaceholder
|
|
||||||
? null
|
|
||||||
: flexRender(
|
|
||||||
header.column.columnDef.header,
|
|
||||||
header.getContext()
|
|
||||||
)}
|
|
||||||
</TableHead>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody className="**:data-[slot=table-cell]:first:w-8">
|
|
||||||
{table.getRowModel().rows?.length ? (
|
|
||||||
<SortableContext
|
|
||||||
items={dataIds}
|
|
||||||
strategy={verticalListSortingStrategy}
|
|
||||||
>
|
|
||||||
{table.getRowModel().rows.map((row) => (
|
|
||||||
<DraggableRow key={row.id} row={row} />
|
|
||||||
))}
|
|
||||||
</SortableContext>
|
|
||||||
) : (
|
|
||||||
<TableRow>
|
|
||||||
<TableCell
|
|
||||||
colSpan={columns.length}
|
|
||||||
className="h-24 text-center"
|
|
||||||
>
|
|
||||||
No results.
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
)}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</DndContext>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center justify-between px-4">
|
|
||||||
<div className="text-muted-foreground hidden flex-1 text-sm lg:flex">
|
|
||||||
{table.getFilteredSelectedRowModel().rows.length} of{" "}
|
|
||||||
{table.getFilteredRowModel().rows.length} row(s) selected.
|
|
||||||
</div>
|
|
||||||
<div className="flex w-full items-center gap-8 lg:w-fit">
|
|
||||||
<div className="hidden items-center gap-2 lg:flex">
|
|
||||||
<Label htmlFor="rows-per-page" className="text-sm font-medium">
|
|
||||||
Rows per page
|
|
||||||
</Label>
|
|
||||||
<Select
|
|
||||||
value={`${table.getState().pagination.pageSize}`}
|
|
||||||
onValueChange={(value) => {
|
|
||||||
table.setPageSize(Number(value))
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SelectTrigger size="sm" className="w-20" id="rows-per-page">
|
|
||||||
<SelectValue
|
|
||||||
placeholder={table.getState().pagination.pageSize}
|
|
||||||
/>
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent side="top">
|
|
||||||
{[10, 20, 30, 40, 50].map((pageSize) => (
|
|
||||||
<SelectItem key={pageSize} value={`${pageSize}`}>
|
|
||||||
{pageSize}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
<div className="flex w-fit items-center justify-center text-sm font-medium">
|
|
||||||
Page {table.getState().pagination.pageIndex + 1} of{" "}
|
|
||||||
{table.getPageCount()}
|
|
||||||
</div>
|
|
||||||
<div className="ml-auto flex items-center gap-2 lg:ml-0">
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
className="hidden h-8 w-8 p-0 lg:flex"
|
|
||||||
onClick={() => table.setPageIndex(0)}
|
|
||||||
disabled={!table.getCanPreviousPage()}
|
|
||||||
>
|
|
||||||
<span className="sr-only">Go to first page</span>
|
|
||||||
<IconChevronsLeft />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
className="size-8"
|
|
||||||
size="icon"
|
|
||||||
onClick={() => table.previousPage()}
|
|
||||||
disabled={!table.getCanPreviousPage()}
|
|
||||||
>
|
|
||||||
<span className="sr-only">Go to previous page</span>
|
|
||||||
<IconChevronLeft />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
className="size-8"
|
|
||||||
size="icon"
|
|
||||||
onClick={() => table.nextPage()}
|
|
||||||
disabled={!table.getCanNextPage()}
|
|
||||||
>
|
|
||||||
<span className="sr-only">Go to next page</span>
|
|
||||||
<IconChevronRight />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
className="hidden size-8 lg:flex"
|
|
||||||
size="icon"
|
|
||||||
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
|
|
||||||
disabled={!table.getCanNextPage()}
|
|
||||||
>
|
|
||||||
<span className="sr-only">Go to last page</span>
|
|
||||||
<IconChevronsRight />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</TabsContent>
|
|
||||||
<TabsContent
|
|
||||||
value="past-performance"
|
|
||||||
className="flex flex-col px-4 lg:px-6"
|
|
||||||
>
|
|
||||||
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed"></div>
|
|
||||||
</TabsContent>
|
|
||||||
<TabsContent value="key-personnel" className="flex flex-col px-4 lg:px-6">
|
|
||||||
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed"></div>
|
|
||||||
</TabsContent>
|
|
||||||
<TabsContent
|
|
||||||
value="focus-documents"
|
|
||||||
className="flex flex-col px-4 lg:px-6"
|
|
||||||
>
|
|
||||||
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed"></div>
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
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: "var(--primary)",
|
|
||||||
},
|
|
||||||
mobile: {
|
|
||||||
label: "Mobile",
|
|
||||||
color: "var(--primary)",
|
|
||||||
},
|
|
||||||
} satisfies ChartConfig
|
|
||||||
|
|
||||||
function TableCellViewer({ item }: { item: z.infer<typeof schema> }) {
|
|
||||||
const isMobile = useIsMobile()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Drawer direction={isMobile ? "bottom" : "right"}>
|
|
||||||
<DrawerTrigger asChild>
|
|
||||||
<Button variant="link" className="text-foreground w-fit px-0 text-left">
|
|
||||||
{item.header}
|
|
||||||
</Button>
|
|
||||||
</DrawerTrigger>
|
|
||||||
<DrawerContent>
|
|
||||||
<DrawerHeader className="gap-1">
|
|
||||||
<DrawerTitle>{item.header}</DrawerTitle>
|
|
||||||
<DrawerDescription>
|
|
||||||
Showing total visitors for the last 6 months
|
|
||||||
</DrawerDescription>
|
|
||||||
</DrawerHeader>
|
|
||||||
<div className="flex flex-col gap-4 overflow-y-auto px-4 text-sm">
|
|
||||||
{!isMobile && (
|
|
||||||
<>
|
|
||||||
<ChartContainer config={chartConfig}>
|
|
||||||
<AreaChart
|
|
||||||
accessibilityLayer
|
|
||||||
data={chartData}
|
|
||||||
margin={{
|
|
||||||
left: 0,
|
|
||||||
right: 10,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CartesianGrid vertical={false} />
|
|
||||||
<XAxis
|
|
||||||
dataKey="month"
|
|
||||||
tickLine={false}
|
|
||||||
axisLine={false}
|
|
||||||
tickMargin={8}
|
|
||||||
tickFormatter={(value) => value.slice(0, 3)}
|
|
||||||
hide
|
|
||||||
/>
|
|
||||||
<ChartTooltip
|
|
||||||
cursor={false}
|
|
||||||
content={<ChartTooltipContent indicator="dot" />}
|
|
||||||
/>
|
|
||||||
<Area
|
|
||||||
dataKey="mobile"
|
|
||||||
type="natural"
|
|
||||||
fill="var(--color-mobile)"
|
|
||||||
fillOpacity={0.6}
|
|
||||||
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>
|
|
||||||
<Separator />
|
|
||||||
<div className="grid gap-2">
|
|
||||||
<div className="flex gap-2 leading-none font-medium">
|
|
||||||
Trending up by 5.2% this month{" "}
|
|
||||||
<IconTrendingUp className="size-4" />
|
|
||||||
</div>
|
|
||||||
<div className="text-muted-foreground">
|
|
||||||
Showing total visitors for the last 6 months. This is just
|
|
||||||
some random text to test the layout. It spans multiple lines
|
|
||||||
and should wrap around.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Separator />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<form className="flex flex-col gap-4">
|
|
||||||
<div className="flex flex-col gap-3">
|
|
||||||
<Label htmlFor="header">Header</Label>
|
|
||||||
<Input id="header" defaultValue={item.header} />
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
|
||||||
<div className="flex flex-col gap-3">
|
|
||||||
<Label htmlFor="type">Type</Label>
|
|
||||||
<Select defaultValue={item.type}>
|
|
||||||
<SelectTrigger id="type" className="w-full">
|
|
||||||
<SelectValue placeholder="Select a type" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="Table of Contents">
|
|
||||||
Table of Contents
|
|
||||||
</SelectItem>
|
|
||||||
<SelectItem value="Executive Summary">
|
|
||||||
Executive Summary
|
|
||||||
</SelectItem>
|
|
||||||
<SelectItem value="Technical Approach">
|
|
||||||
Technical Approach
|
|
||||||
</SelectItem>
|
|
||||||
<SelectItem value="Design">Design</SelectItem>
|
|
||||||
<SelectItem value="Capabilities">Capabilities</SelectItem>
|
|
||||||
<SelectItem value="Focus Documents">
|
|
||||||
Focus Documents
|
|
||||||
</SelectItem>
|
|
||||||
<SelectItem value="Narrative">Narrative</SelectItem>
|
|
||||||
<SelectItem value="Cover Page">Cover Page</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-3">
|
|
||||||
<Label htmlFor="status">Status</Label>
|
|
||||||
<Select defaultValue={item.status}>
|
|
||||||
<SelectTrigger id="status" className="w-full">
|
|
||||||
<SelectValue placeholder="Select a status" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="Done">Done</SelectItem>
|
|
||||||
<SelectItem value="In Progress">In Progress</SelectItem>
|
|
||||||
<SelectItem value="Not Started">Not Started</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
|
||||||
<div className="flex flex-col gap-3">
|
|
||||||
<Label htmlFor="target">Target</Label>
|
|
||||||
<Input id="target" defaultValue={item.target} />
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-3">
|
|
||||||
<Label htmlFor="limit">Limit</Label>
|
|
||||||
<Input id="limit" defaultValue={item.limit} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-3">
|
|
||||||
<Label htmlFor="reviewer">Reviewer</Label>
|
|
||||||
<Select defaultValue={item.reviewer}>
|
|
||||||
<SelectTrigger id="reviewer" className="w-full">
|
|
||||||
<SelectValue placeholder="Select a reviewer" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="Eddie Lake">Eddie Lake</SelectItem>
|
|
||||||
<SelectItem value="Jamik Tashpulatov">
|
|
||||||
Jamik Tashpulatov
|
|
||||||
</SelectItem>
|
|
||||||
<SelectItem value="Emily Whalen">Emily Whalen</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<DrawerFooter>
|
|
||||||
<Button>Submit</Button>
|
|
||||||
<DrawerClose asChild>
|
|
||||||
<Button variant="outline">Done</Button>
|
|
||||||
</DrawerClose>
|
|
||||||
</DrawerFooter>
|
|
||||||
</DrawerContent>
|
|
||||||
</Drawer>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import { IconBrightness } from "@tabler/icons-react"
|
|
||||||
import { useTheme } from "next-themes"
|
|
||||||
|
|
||||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
|
||||||
|
|
||||||
export function ModeToggle() {
|
|
||||||
const { setTheme, resolvedTheme } = useTheme()
|
|
||||||
|
|
||||||
const toggleTheme = React.useCallback(() => {
|
|
||||||
setTheme(resolvedTheme === "dark" ? "light" : "dark")
|
|
||||||
}, [resolvedTheme, setTheme])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
variant="secondary"
|
|
||||||
size="icon"
|
|
||||||
className="group/toggle size-8"
|
|
||||||
onClick={toggleTheme}
|
|
||||||
>
|
|
||||||
<IconBrightness />
|
|
||||||
<span className="sr-only">Toggle theme</span>
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import {
|
|
||||||
IconDots,
|
|
||||||
IconFolder,
|
|
||||||
IconShare3,
|
|
||||||
IconTrash,
|
|
||||||
type Icon,
|
|
||||||
} from "@tabler/icons-react"
|
|
||||||
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuSeparator,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "@/registry/new-york-v4/ui/dropdown-menu"
|
|
||||||
import {
|
|
||||||
SidebarGroup,
|
|
||||||
SidebarGroupLabel,
|
|
||||||
SidebarMenu,
|
|
||||||
SidebarMenuAction,
|
|
||||||
SidebarMenuButton,
|
|
||||||
SidebarMenuItem,
|
|
||||||
useSidebar,
|
|
||||||
} from "@/registry/new-york-v4/ui/sidebar"
|
|
||||||
|
|
||||||
export function NavDocuments({
|
|
||||||
items,
|
|
||||||
}: {
|
|
||||||
items: {
|
|
||||||
name: string
|
|
||||||
url: string
|
|
||||||
icon: Icon
|
|
||||||
}[]
|
|
||||||
}) {
|
|
||||||
const { isMobile } = useSidebar()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
|
|
||||||
<SidebarGroupLabel>Documents</SidebarGroupLabel>
|
|
||||||
<SidebarMenu>
|
|
||||||
{items.map((item) => (
|
|
||||||
<SidebarMenuItem key={item.name}>
|
|
||||||
<SidebarMenuButton asChild>
|
|
||||||
<a href={item.url}>
|
|
||||||
<item.icon />
|
|
||||||
<span>{item.name}</span>
|
|
||||||
</a>
|
|
||||||
</SidebarMenuButton>
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<SidebarMenuAction
|
|
||||||
showOnHover
|
|
||||||
className="data-[state=open]:bg-accent rounded-sm"
|
|
||||||
>
|
|
||||||
<IconDots />
|
|
||||||
<span className="sr-only">More</span>
|
|
||||||
</SidebarMenuAction>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent
|
|
||||||
className="w-24 rounded-lg"
|
|
||||||
side={isMobile ? "bottom" : "right"}
|
|
||||||
align={isMobile ? "end" : "start"}
|
|
||||||
>
|
|
||||||
<DropdownMenuItem>
|
|
||||||
<IconFolder />
|
|
||||||
<span>Open</span>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem>
|
|
||||||
<IconShare3 />
|
|
||||||
<span>Share</span>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<DropdownMenuItem variant="destructive">
|
|
||||||
<IconTrash />
|
|
||||||
<span>Delete</span>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
</SidebarMenuItem>
|
|
||||||
))}
|
|
||||||
<SidebarMenuItem>
|
|
||||||
<SidebarMenuButton className="text-sidebar-foreground/70">
|
|
||||||
<IconDots className="text-sidebar-foreground/70" />
|
|
||||||
<span>More</span>
|
|
||||||
</SidebarMenuButton>
|
|
||||||
</SidebarMenuItem>
|
|
||||||
</SidebarMenu>
|
|
||||||
</SidebarGroup>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { IconCirclePlusFilled, IconMail, type Icon } from "@tabler/icons-react"
|
|
||||||
|
|
||||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
|
||||||
import {
|
|
||||||
SidebarGroup,
|
|
||||||
SidebarGroupContent,
|
|
||||||
SidebarMenu,
|
|
||||||
SidebarMenuButton,
|
|
||||||
SidebarMenuItem,
|
|
||||||
} from "@/registry/new-york-v4/ui/sidebar"
|
|
||||||
|
|
||||||
export function NavMain({
|
|
||||||
items,
|
|
||||||
}: {
|
|
||||||
items: {
|
|
||||||
title: string
|
|
||||||
url: string
|
|
||||||
icon?: Icon
|
|
||||||
}[]
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<SidebarGroup>
|
|
||||||
<SidebarGroupContent className="flex flex-col gap-2">
|
|
||||||
<SidebarMenu>
|
|
||||||
<SidebarMenuItem className="flex items-center gap-2">
|
|
||||||
<SidebarMenuButton
|
|
||||||
tooltip="Quick Create"
|
|
||||||
className="bg-primary text-primary-foreground hover:bg-primary/90 hover:text-primary-foreground active:bg-primary/90 active:text-primary-foreground min-w-8 duration-200 ease-linear"
|
|
||||||
>
|
|
||||||
<IconCirclePlusFilled />
|
|
||||||
<span>Quick Create</span>
|
|
||||||
</SidebarMenuButton>
|
|
||||||
<Button
|
|
||||||
size="icon"
|
|
||||||
className="size-8 group-data-[collapsible=icon]:opacity-0"
|
|
||||||
variant="outline"
|
|
||||||
>
|
|
||||||
<IconMail />
|
|
||||||
<span className="sr-only">Inbox</span>
|
|
||||||
</Button>
|
|
||||||
</SidebarMenuItem>
|
|
||||||
</SidebarMenu>
|
|
||||||
<SidebarMenu>
|
|
||||||
{items.map((item) => (
|
|
||||||
<SidebarMenuItem key={item.title}>
|
|
||||||
<SidebarMenuButton tooltip={item.title}>
|
|
||||||
{item.icon && <item.icon />}
|
|
||||||
<span>{item.title}</span>
|
|
||||||
</SidebarMenuButton>
|
|
||||||
</SidebarMenuItem>
|
|
||||||
))}
|
|
||||||
</SidebarMenu>
|
|
||||||
</SidebarGroupContent>
|
|
||||||
</SidebarGroup>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import { IconBrightness, type Icon } from "@tabler/icons-react"
|
|
||||||
import { useTheme } from "next-themes"
|
|
||||||
|
|
||||||
import {
|
|
||||||
SidebarGroup,
|
|
||||||
SidebarGroupContent,
|
|
||||||
SidebarMenu,
|
|
||||||
SidebarMenuButton,
|
|
||||||
SidebarMenuItem,
|
|
||||||
} from "@/registry/new-york-v4/ui/sidebar"
|
|
||||||
import { Skeleton } from "@/registry/new-york-v4/ui/skeleton"
|
|
||||||
import { Switch } from "@/registry/new-york-v4/ui/switch"
|
|
||||||
|
|
||||||
export function NavSecondary({
|
|
||||||
items,
|
|
||||||
...props
|
|
||||||
}: {
|
|
||||||
items: {
|
|
||||||
title: string
|
|
||||||
url: string
|
|
||||||
icon: Icon
|
|
||||||
}[]
|
|
||||||
} & React.ComponentPropsWithoutRef<typeof SidebarGroup>) {
|
|
||||||
const { resolvedTheme, setTheme } = useTheme()
|
|
||||||
const [mounted, setMounted] = React.useState(false)
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
setMounted(true)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SidebarGroup {...props}>
|
|
||||||
<SidebarGroupContent>
|
|
||||||
<SidebarMenu>
|
|
||||||
{items.map((item) => (
|
|
||||||
<SidebarMenuItem key={item.title}>
|
|
||||||
<SidebarMenuButton asChild>
|
|
||||||
<a href={item.url}>
|
|
||||||
<item.icon />
|
|
||||||
<span>{item.title}</span>
|
|
||||||
</a>
|
|
||||||
</SidebarMenuButton>
|
|
||||||
</SidebarMenuItem>
|
|
||||||
))}
|
|
||||||
<SidebarMenuItem className="group-data-[collapsible=icon]:hidden">
|
|
||||||
<SidebarMenuButton asChild>
|
|
||||||
<label>
|
|
||||||
<IconBrightness />
|
|
||||||
<span>Dark Mode</span>
|
|
||||||
{mounted ? (
|
|
||||||
<Switch
|
|
||||||
className="ml-auto"
|
|
||||||
checked={resolvedTheme !== "light"}
|
|
||||||
onCheckedChange={() =>
|
|
||||||
setTheme(resolvedTheme === "dark" ? "light" : "dark")
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Skeleton className="ml-auto h-4 w-8 rounded-full" />
|
|
||||||
)}
|
|
||||||
</label>
|
|
||||||
</SidebarMenuButton>
|
|
||||||
</SidebarMenuItem>
|
|
||||||
</SidebarMenu>
|
|
||||||
</SidebarGroupContent>
|
|
||||||
</SidebarGroup>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import {
|
|
||||||
IconCreditCard,
|
|
||||||
IconDotsVertical,
|
|
||||||
IconLogout,
|
|
||||||
IconNotification,
|
|
||||||
IconUserCircle,
|
|
||||||
} from "@tabler/icons-react"
|
|
||||||
|
|
||||||
import {
|
|
||||||
Avatar,
|
|
||||||
AvatarFallback,
|
|
||||||
AvatarImage,
|
|
||||||
} from "@/registry/new-york-v4/ui/avatar"
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuGroup,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuLabel,
|
|
||||||
DropdownMenuSeparator,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "@/registry/new-york-v4/ui/dropdown-menu"
|
|
||||||
import {
|
|
||||||
SidebarMenu,
|
|
||||||
SidebarMenuButton,
|
|
||||||
SidebarMenuItem,
|
|
||||||
useSidebar,
|
|
||||||
} from "@/registry/new-york-v4/ui/sidebar"
|
|
||||||
|
|
||||||
export function NavUser({
|
|
||||||
user,
|
|
||||||
}: {
|
|
||||||
user: {
|
|
||||||
name: string
|
|
||||||
email: string
|
|
||||||
avatar: string
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
const { isMobile } = useSidebar()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SidebarMenu>
|
|
||||||
<SidebarMenuItem>
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<SidebarMenuButton
|
|
||||||
size="lg"
|
|
||||||
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
|
||||||
>
|
|
||||||
<Avatar className="h-8 w-8 rounded-lg grayscale">
|
|
||||||
<AvatarImage src={user.avatar} alt={user.name} />
|
|
||||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
|
||||||
<span className="truncate font-medium">{user.name}</span>
|
|
||||||
<span className="text-muted-foreground truncate text-xs">
|
|
||||||
{user.email}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<IconDotsVertical className="ml-auto size-4" />
|
|
||||||
</SidebarMenuButton>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent
|
|
||||||
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
|
|
||||||
side={isMobile ? "bottom" : "right"}
|
|
||||||
align="end"
|
|
||||||
sideOffset={4}
|
|
||||||
>
|
|
||||||
<DropdownMenuLabel className="p-0 font-normal">
|
|
||||||
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
|
||||||
<Avatar className="h-8 w-8 rounded-lg">
|
|
||||||
<AvatarImage src={user.avatar} alt={user.name} />
|
|
||||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
|
||||||
<span className="truncate font-medium">{user.name}</span>
|
|
||||||
<span className="text-muted-foreground truncate text-xs">
|
|
||||||
{user.email}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</DropdownMenuLabel>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<DropdownMenuGroup>
|
|
||||||
<DropdownMenuItem>
|
|
||||||
<IconUserCircle />
|
|
||||||
Account
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem>
|
|
||||||
<IconCreditCard />
|
|
||||||
Billing
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem>
|
|
||||||
<IconNotification />
|
|
||||||
Notifications
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuGroup>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<DropdownMenuItem>
|
|
||||||
<IconLogout />
|
|
||||||
Log out
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
</SidebarMenuItem>
|
|
||||||
</SidebarMenu>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
import { IconTrendingDown, IconTrendingUp } from "@tabler/icons-react"
|
|
||||||
|
|
||||||
import { Badge } from "@/registry/new-york-v4/ui/badge"
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardAction,
|
|
||||||
CardDescription,
|
|
||||||
CardFooter,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from "@/registry/new-york-v4/ui/card"
|
|
||||||
|
|
||||||
export function SectionCards() {
|
|
||||||
return (
|
|
||||||
<div className="*:data-[slot=card]:from-primary/5 *:data-[slot=card]:to-card dark:*:data-[slot=card]:bg-card grid grid-cols-1 gap-4 px-4 *:data-[slot=card]:bg-gradient-to-t *:data-[slot=card]:shadow-xs lg:px-6 @xl/main:grid-cols-2 @5xl/main:grid-cols-4">
|
|
||||||
<Card className="@container/card">
|
|
||||||
<CardHeader>
|
|
||||||
<CardDescription>Total Revenue</CardDescription>
|
|
||||||
<CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
|
|
||||||
$1,250.00
|
|
||||||
</CardTitle>
|
|
||||||
<CardAction>
|
|
||||||
<Badge variant="outline">
|
|
||||||
<IconTrendingUp />
|
|
||||||
+12.5%
|
|
||||||
</Badge>
|
|
||||||
</CardAction>
|
|
||||||
</CardHeader>
|
|
||||||
<CardFooter className="flex-col items-start gap-1.5 text-sm">
|
|
||||||
<div className="line-clamp-1 flex gap-2 font-medium">
|
|
||||||
Trending up this month <IconTrendingUp className="size-4" />
|
|
||||||
</div>
|
|
||||||
<div className="text-muted-foreground">
|
|
||||||
Visitors for the last 6 months
|
|
||||||
</div>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
<Card className="@container/card">
|
|
||||||
<CardHeader>
|
|
||||||
<CardDescription>New Customers</CardDescription>
|
|
||||||
<CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
|
|
||||||
1,234
|
|
||||||
</CardTitle>
|
|
||||||
<CardAction>
|
|
||||||
<Badge variant="outline">
|
|
||||||
<IconTrendingDown />
|
|
||||||
-20%
|
|
||||||
</Badge>
|
|
||||||
</CardAction>
|
|
||||||
</CardHeader>
|
|
||||||
<CardFooter className="flex-col items-start gap-1.5 text-sm">
|
|
||||||
<div className="line-clamp-1 flex gap-2 font-medium">
|
|
||||||
Down 20% this period <IconTrendingDown className="size-4" />
|
|
||||||
</div>
|
|
||||||
<div className="text-muted-foreground">
|
|
||||||
Acquisition needs attention
|
|
||||||
</div>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
<Card className="@container/card">
|
|
||||||
<CardHeader>
|
|
||||||
<CardDescription>Active Accounts</CardDescription>
|
|
||||||
<CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
|
|
||||||
45,678
|
|
||||||
</CardTitle>
|
|
||||||
<CardAction>
|
|
||||||
<Badge variant="outline">
|
|
||||||
<IconTrendingUp />
|
|
||||||
+12.5%
|
|
||||||
</Badge>
|
|
||||||
</CardAction>
|
|
||||||
</CardHeader>
|
|
||||||
<CardFooter className="flex-col items-start gap-1.5 text-sm">
|
|
||||||
<div className="line-clamp-1 flex gap-2 font-medium">
|
|
||||||
Strong user retention <IconTrendingUp className="size-4" />
|
|
||||||
</div>
|
|
||||||
<div className="text-muted-foreground">Engagement exceed targets</div>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
<Card className="@container/card">
|
|
||||||
<CardHeader>
|
|
||||||
<CardDescription>Growth Rate</CardDescription>
|
|
||||||
<CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
|
|
||||||
4.5%
|
|
||||||
</CardTitle>
|
|
||||||
<CardAction>
|
|
||||||
<Badge variant="outline">
|
|
||||||
<IconTrendingUp />
|
|
||||||
+4.5%
|
|
||||||
</Badge>
|
|
||||||
</CardAction>
|
|
||||||
</CardHeader>
|
|
||||||
<CardFooter className="flex-col items-start gap-1.5 text-sm">
|
|
||||||
<div className="line-clamp-1 flex gap-2 font-medium">
|
|
||||||
Steady performance increase <IconTrendingUp className="size-4" />
|
|
||||||
</div>
|
|
||||||
<div className="text-muted-foreground">Meets growth projections</div>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
|
||||||
import { Separator } from "@/registry/new-york-v4/ui/separator"
|
|
||||||
import { SidebarTrigger } from "@/registry/new-york-v4/ui/sidebar"
|
|
||||||
import { ModeToggle } from "@/app/(examples)/dashboard/components/mode-toggle"
|
|
||||||
import { ThemeSelector } from "@/app/(examples)/dashboard/components/theme-selector"
|
|
||||||
|
|
||||||
export function SiteHeader() {
|
|
||||||
return (
|
|
||||||
<header className="flex h-(--header-height) shrink-0 items-center gap-2 border-b transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-(--header-height)">
|
|
||||||
<div className="flex w-full items-center gap-1 px-4 lg:gap-2 lg:px-6">
|
|
||||||
<SidebarTrigger className="-ml-1" />
|
|
||||||
<Separator
|
|
||||||
orientation="vertical"
|
|
||||||
className="mx-2 data-[orientation=vertical]:h-4"
|
|
||||||
/>
|
|
||||||
<h1 className="text-base font-medium">Documents</h1>
|
|
||||||
<div className="ml-auto flex items-center gap-2">
|
|
||||||
<Button variant="ghost" asChild size="sm" className="hidden sm:flex">
|
|
||||||
<a
|
|
||||||
href="https://github.com/shadcn-ui/ui/tree/main/apps/v4/app/(examples)/dashboard"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
className="dark:text-foreground"
|
|
||||||
>
|
|
||||||
GitHub
|
|
||||||
</a>
|
|
||||||
</Button>
|
|
||||||
<ThemeSelector />
|
|
||||||
<ModeToggle />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { useThemeConfig } from "@/components/active-theme"
|
|
||||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectGroup,
|
|
||||||
SelectItem,
|
|
||||||
SelectLabel,
|
|
||||||
SelectSeparator,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@/registry/new-york-v4/ui/select"
|
|
||||||
|
|
||||||
const DEFAULT_THEMES = [
|
|
||||||
{
|
|
||||||
name: "Default",
|
|
||||||
value: "default",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Blue",
|
|
||||||
value: "blue",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Green",
|
|
||||||
value: "green",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Amber",
|
|
||||||
value: "amber",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const SCALED_THEMES = [
|
|
||||||
{
|
|
||||||
name: "Default",
|
|
||||||
value: "default-scaled",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Blue",
|
|
||||||
value: "blue-scaled",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const MONO_THEMES = [
|
|
||||||
{
|
|
||||||
name: "Mono",
|
|
||||||
value: "mono-scaled",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
export function ThemeSelector() {
|
|
||||||
const { activeTheme, setActiveTheme } = useThemeConfig()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Label htmlFor="theme-selector" className="sr-only">
|
|
||||||
Theme
|
|
||||||
</Label>
|
|
||||||
<Select value={activeTheme} onValueChange={setActiveTheme}>
|
|
||||||
<SelectTrigger
|
|
||||||
id="theme-selector"
|
|
||||||
size="sm"
|
|
||||||
className="justify-start *:data-[slot=select-value]:w-12"
|
|
||||||
>
|
|
||||||
<span className="text-muted-foreground hidden sm:block">
|
|
||||||
Select a theme:
|
|
||||||
</span>
|
|
||||||
<span className="text-muted-foreground block sm:hidden">Theme</span>
|
|
||||||
<SelectValue placeholder="Select a theme" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent align="end">
|
|
||||||
<SelectGroup>
|
|
||||||
<SelectLabel>Default</SelectLabel>
|
|
||||||
{DEFAULT_THEMES.map((theme) => (
|
|
||||||
<SelectItem key={theme.name} value={theme.value}>
|
|
||||||
{theme.name}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectGroup>
|
|
||||||
<SelectSeparator />
|
|
||||||
<SelectGroup>
|
|
||||||
<SelectLabel>Scaled</SelectLabel>
|
|
||||||
{SCALED_THEMES.map((theme) => (
|
|
||||||
<SelectItem key={theme.name} value={theme.value}>
|
|
||||||
{theme.name}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectGroup>
|
|
||||||
<SelectGroup>
|
|
||||||
<SelectLabel>Monospaced</SelectLabel>
|
|
||||||
{MONO_THEMES.map((theme) => (
|
|
||||||
<SelectItem key={theme.name} value={theme.value}>
|
|
||||||
{theme.name}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectGroup>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,614 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"header": "Cover page",
|
|
||||||
"type": "Cover page",
|
|
||||||
"status": "In Process",
|
|
||||||
"target": "18",
|
|
||||||
"limit": "5",
|
|
||||||
"reviewer": "Eddie Lake"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"header": "Table of contents",
|
|
||||||
"type": "Table of contents",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "29",
|
|
||||||
"limit": "24",
|
|
||||||
"reviewer": "Eddie Lake"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 3,
|
|
||||||
"header": "Executive summary",
|
|
||||||
"type": "Narrative",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "10",
|
|
||||||
"limit": "13",
|
|
||||||
"reviewer": "Eddie Lake"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 4,
|
|
||||||
"header": "Technical approach",
|
|
||||||
"type": "Narrative",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "27",
|
|
||||||
"limit": "23",
|
|
||||||
"reviewer": "Jamik Tashpulatov"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 5,
|
|
||||||
"header": "Design",
|
|
||||||
"type": "Narrative",
|
|
||||||
"status": "In Process",
|
|
||||||
"target": "2",
|
|
||||||
"limit": "16",
|
|
||||||
"reviewer": "Jamik Tashpulatov"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 6,
|
|
||||||
"header": "Capabilities",
|
|
||||||
"type": "Narrative",
|
|
||||||
"status": "In Process",
|
|
||||||
"target": "20",
|
|
||||||
"limit": "8",
|
|
||||||
"reviewer": "Jamik Tashpulatov"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 7,
|
|
||||||
"header": "Integration with existing systems",
|
|
||||||
"type": "Narrative",
|
|
||||||
"status": "In Process",
|
|
||||||
"target": "19",
|
|
||||||
"limit": "21",
|
|
||||||
"reviewer": "Jamik Tashpulatov"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 8,
|
|
||||||
"header": "Innovation and Advantages",
|
|
||||||
"type": "Narrative",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "25",
|
|
||||||
"limit": "26",
|
|
||||||
"reviewer": "Assign reviewer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 9,
|
|
||||||
"header": "Overview of EMR's Innovative Solutions",
|
|
||||||
"type": "Technical content",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "7",
|
|
||||||
"limit": "23",
|
|
||||||
"reviewer": "Assign reviewer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 10,
|
|
||||||
"header": "Advanced Algorithms and Machine Learning",
|
|
||||||
"type": "Narrative",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "30",
|
|
||||||
"limit": "28",
|
|
||||||
"reviewer": "Assign reviewer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 11,
|
|
||||||
"header": "Adaptive Communication Protocols",
|
|
||||||
"type": "Narrative",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "9",
|
|
||||||
"limit": "31",
|
|
||||||
"reviewer": "Assign reviewer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 12,
|
|
||||||
"header": "Advantages Over Current Technologies",
|
|
||||||
"type": "Narrative",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "12",
|
|
||||||
"limit": "0",
|
|
||||||
"reviewer": "Assign reviewer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 13,
|
|
||||||
"header": "Past Performance",
|
|
||||||
"type": "Narrative",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "22",
|
|
||||||
"limit": "33",
|
|
||||||
"reviewer": "Assign reviewer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 14,
|
|
||||||
"header": "Customer Feedback and Satisfaction Levels",
|
|
||||||
"type": "Narrative",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "15",
|
|
||||||
"limit": "34",
|
|
||||||
"reviewer": "Assign reviewer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 15,
|
|
||||||
"header": "Implementation Challenges and Solutions",
|
|
||||||
"type": "Narrative",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "3",
|
|
||||||
"limit": "35",
|
|
||||||
"reviewer": "Assign reviewer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 16,
|
|
||||||
"header": "Security Measures and Data Protection Policies",
|
|
||||||
"type": "Narrative",
|
|
||||||
"status": "In Process",
|
|
||||||
"target": "6",
|
|
||||||
"limit": "36",
|
|
||||||
"reviewer": "Assign reviewer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 17,
|
|
||||||
"header": "Scalability and Future Proofing",
|
|
||||||
"type": "Narrative",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "4",
|
|
||||||
"limit": "37",
|
|
||||||
"reviewer": "Assign reviewer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 18,
|
|
||||||
"header": "Cost-Benefit Analysis",
|
|
||||||
"type": "Plain language",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "14",
|
|
||||||
"limit": "38",
|
|
||||||
"reviewer": "Assign reviewer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 19,
|
|
||||||
"header": "User Training and Onboarding Experience",
|
|
||||||
"type": "Narrative",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "17",
|
|
||||||
"limit": "39",
|
|
||||||
"reviewer": "Assign reviewer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 20,
|
|
||||||
"header": "Future Development Roadmap",
|
|
||||||
"type": "Narrative",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "11",
|
|
||||||
"limit": "40",
|
|
||||||
"reviewer": "Assign reviewer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 21,
|
|
||||||
"header": "System Architecture Overview",
|
|
||||||
"type": "Technical content",
|
|
||||||
"status": "In Process",
|
|
||||||
"target": "24",
|
|
||||||
"limit": "18",
|
|
||||||
"reviewer": "Maya Johnson"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 22,
|
|
||||||
"header": "Risk Management Plan",
|
|
||||||
"type": "Narrative",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "15",
|
|
||||||
"limit": "22",
|
|
||||||
"reviewer": "Carlos Rodriguez"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 23,
|
|
||||||
"header": "Compliance Documentation",
|
|
||||||
"type": "Legal",
|
|
||||||
"status": "In Process",
|
|
||||||
"target": "31",
|
|
||||||
"limit": "27",
|
|
||||||
"reviewer": "Sarah Chen"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 24,
|
|
||||||
"header": "API Documentation",
|
|
||||||
"type": "Technical content",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "8",
|
|
||||||
"limit": "12",
|
|
||||||
"reviewer": "Raj Patel"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 25,
|
|
||||||
"header": "User Interface Mockups",
|
|
||||||
"type": "Visual",
|
|
||||||
"status": "In Process",
|
|
||||||
"target": "19",
|
|
||||||
"limit": "25",
|
|
||||||
"reviewer": "Leila Ahmadi"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 26,
|
|
||||||
"header": "Database Schema",
|
|
||||||
"type": "Technical content",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "22",
|
|
||||||
"limit": "20",
|
|
||||||
"reviewer": "Thomas Wilson"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 27,
|
|
||||||
"header": "Testing Methodology",
|
|
||||||
"type": "Technical content",
|
|
||||||
"status": "In Process",
|
|
||||||
"target": "17",
|
|
||||||
"limit": "14",
|
|
||||||
"reviewer": "Assign reviewer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 28,
|
|
||||||
"header": "Deployment Strategy",
|
|
||||||
"type": "Narrative",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "26",
|
|
||||||
"limit": "30",
|
|
||||||
"reviewer": "Eddie Lake"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 29,
|
|
||||||
"header": "Budget Breakdown",
|
|
||||||
"type": "Financial",
|
|
||||||
"status": "In Process",
|
|
||||||
"target": "13",
|
|
||||||
"limit": "16",
|
|
||||||
"reviewer": "Jamik Tashpulatov"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 30,
|
|
||||||
"header": "Market Analysis",
|
|
||||||
"type": "Research",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "29",
|
|
||||||
"limit": "32",
|
|
||||||
"reviewer": "Sophia Martinez"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 31,
|
|
||||||
"header": "Competitor Comparison",
|
|
||||||
"type": "Research",
|
|
||||||
"status": "In Process",
|
|
||||||
"target": "21",
|
|
||||||
"limit": "19",
|
|
||||||
"reviewer": "Assign reviewer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 32,
|
|
||||||
"header": "Maintenance Plan",
|
|
||||||
"type": "Technical content",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "16",
|
|
||||||
"limit": "23",
|
|
||||||
"reviewer": "Alex Thompson"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 33,
|
|
||||||
"header": "User Personas",
|
|
||||||
"type": "Research",
|
|
||||||
"status": "In Process",
|
|
||||||
"target": "27",
|
|
||||||
"limit": "24",
|
|
||||||
"reviewer": "Nina Patel"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 34,
|
|
||||||
"header": "Accessibility Compliance",
|
|
||||||
"type": "Legal",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "18",
|
|
||||||
"limit": "21",
|
|
||||||
"reviewer": "Assign reviewer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 35,
|
|
||||||
"header": "Performance Metrics",
|
|
||||||
"type": "Technical content",
|
|
||||||
"status": "In Process",
|
|
||||||
"target": "23",
|
|
||||||
"limit": "26",
|
|
||||||
"reviewer": "David Kim"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 36,
|
|
||||||
"header": "Disaster Recovery Plan",
|
|
||||||
"type": "Technical content",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "14",
|
|
||||||
"limit": "17",
|
|
||||||
"reviewer": "Jamik Tashpulatov"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 37,
|
|
||||||
"header": "Third-party Integrations",
|
|
||||||
"type": "Technical content",
|
|
||||||
"status": "In Process",
|
|
||||||
"target": "25",
|
|
||||||
"limit": "28",
|
|
||||||
"reviewer": "Eddie Lake"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 38,
|
|
||||||
"header": "User Feedback Summary",
|
|
||||||
"type": "Research",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "20",
|
|
||||||
"limit": "15",
|
|
||||||
"reviewer": "Assign reviewer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 39,
|
|
||||||
"header": "Localization Strategy",
|
|
||||||
"type": "Narrative",
|
|
||||||
"status": "In Process",
|
|
||||||
"target": "12",
|
|
||||||
"limit": "19",
|
|
||||||
"reviewer": "Maria Garcia"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 40,
|
|
||||||
"header": "Mobile Compatibility",
|
|
||||||
"type": "Technical content",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "28",
|
|
||||||
"limit": "31",
|
|
||||||
"reviewer": "James Wilson"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 41,
|
|
||||||
"header": "Data Migration Plan",
|
|
||||||
"type": "Technical content",
|
|
||||||
"status": "In Process",
|
|
||||||
"target": "19",
|
|
||||||
"limit": "22",
|
|
||||||
"reviewer": "Assign reviewer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 42,
|
|
||||||
"header": "Quality Assurance Protocols",
|
|
||||||
"type": "Technical content",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "30",
|
|
||||||
"limit": "33",
|
|
||||||
"reviewer": "Priya Singh"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 43,
|
|
||||||
"header": "Stakeholder Analysis",
|
|
||||||
"type": "Research",
|
|
||||||
"status": "In Process",
|
|
||||||
"target": "11",
|
|
||||||
"limit": "14",
|
|
||||||
"reviewer": "Eddie Lake"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 44,
|
|
||||||
"header": "Environmental Impact Assessment",
|
|
||||||
"type": "Research",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "24",
|
|
||||||
"limit": "27",
|
|
||||||
"reviewer": "Assign reviewer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 45,
|
|
||||||
"header": "Intellectual Property Rights",
|
|
||||||
"type": "Legal",
|
|
||||||
"status": "In Process",
|
|
||||||
"target": "17",
|
|
||||||
"limit": "20",
|
|
||||||
"reviewer": "Sarah Johnson"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 46,
|
|
||||||
"header": "Customer Support Framework",
|
|
||||||
"type": "Narrative",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "22",
|
|
||||||
"limit": "25",
|
|
||||||
"reviewer": "Jamik Tashpulatov"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 47,
|
|
||||||
"header": "Version Control Strategy",
|
|
||||||
"type": "Technical content",
|
|
||||||
"status": "In Process",
|
|
||||||
"target": "15",
|
|
||||||
"limit": "18",
|
|
||||||
"reviewer": "Assign reviewer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 48,
|
|
||||||
"header": "Continuous Integration Pipeline",
|
|
||||||
"type": "Technical content",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "26",
|
|
||||||
"limit": "29",
|
|
||||||
"reviewer": "Michael Chen"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 49,
|
|
||||||
"header": "Regulatory Compliance",
|
|
||||||
"type": "Legal",
|
|
||||||
"status": "In Process",
|
|
||||||
"target": "13",
|
|
||||||
"limit": "16",
|
|
||||||
"reviewer": "Assign reviewer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 50,
|
|
||||||
"header": "User Authentication System",
|
|
||||||
"type": "Technical content",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "28",
|
|
||||||
"limit": "31",
|
|
||||||
"reviewer": "Eddie Lake"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 51,
|
|
||||||
"header": "Data Analytics Framework",
|
|
||||||
"type": "Technical content",
|
|
||||||
"status": "In Process",
|
|
||||||
"target": "21",
|
|
||||||
"limit": "24",
|
|
||||||
"reviewer": "Jamik Tashpulatov"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 52,
|
|
||||||
"header": "Cloud Infrastructure",
|
|
||||||
"type": "Technical content",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "16",
|
|
||||||
"limit": "19",
|
|
||||||
"reviewer": "Assign reviewer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 53,
|
|
||||||
"header": "Network Security Measures",
|
|
||||||
"type": "Technical content",
|
|
||||||
"status": "In Process",
|
|
||||||
"target": "29",
|
|
||||||
"limit": "32",
|
|
||||||
"reviewer": "Lisa Wong"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 54,
|
|
||||||
"header": "Project Timeline",
|
|
||||||
"type": "Planning",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "14",
|
|
||||||
"limit": "17",
|
|
||||||
"reviewer": "Eddie Lake"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 55,
|
|
||||||
"header": "Resource Allocation",
|
|
||||||
"type": "Planning",
|
|
||||||
"status": "In Process",
|
|
||||||
"target": "27",
|
|
||||||
"limit": "30",
|
|
||||||
"reviewer": "Assign reviewer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 56,
|
|
||||||
"header": "Team Structure and Roles",
|
|
||||||
"type": "Planning",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "20",
|
|
||||||
"limit": "23",
|
|
||||||
"reviewer": "Jamik Tashpulatov"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 57,
|
|
||||||
"header": "Communication Protocols",
|
|
||||||
"type": "Planning",
|
|
||||||
"status": "In Process",
|
|
||||||
"target": "15",
|
|
||||||
"limit": "18",
|
|
||||||
"reviewer": "Assign reviewer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 58,
|
|
||||||
"header": "Success Metrics",
|
|
||||||
"type": "Planning",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "30",
|
|
||||||
"limit": "33",
|
|
||||||
"reviewer": "Eddie Lake"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 59,
|
|
||||||
"header": "Internationalization Support",
|
|
||||||
"type": "Technical content",
|
|
||||||
"status": "In Process",
|
|
||||||
"target": "23",
|
|
||||||
"limit": "26",
|
|
||||||
"reviewer": "Jamik Tashpulatov"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 60,
|
|
||||||
"header": "Backup and Recovery Procedures",
|
|
||||||
"type": "Technical content",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "18",
|
|
||||||
"limit": "21",
|
|
||||||
"reviewer": "Assign reviewer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 61,
|
|
||||||
"header": "Monitoring and Alerting System",
|
|
||||||
"type": "Technical content",
|
|
||||||
"status": "In Process",
|
|
||||||
"target": "25",
|
|
||||||
"limit": "28",
|
|
||||||
"reviewer": "Daniel Park"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 62,
|
|
||||||
"header": "Code Review Guidelines",
|
|
||||||
"type": "Technical content",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "12",
|
|
||||||
"limit": "15",
|
|
||||||
"reviewer": "Eddie Lake"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 63,
|
|
||||||
"header": "Documentation Standards",
|
|
||||||
"type": "Technical content",
|
|
||||||
"status": "In Process",
|
|
||||||
"target": "27",
|
|
||||||
"limit": "30",
|
|
||||||
"reviewer": "Jamik Tashpulatov"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 64,
|
|
||||||
"header": "Release Management Process",
|
|
||||||
"type": "Planning",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "22",
|
|
||||||
"limit": "25",
|
|
||||||
"reviewer": "Assign reviewer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 65,
|
|
||||||
"header": "Feature Prioritization Matrix",
|
|
||||||
"type": "Planning",
|
|
||||||
"status": "In Process",
|
|
||||||
"target": "19",
|
|
||||||
"limit": "22",
|
|
||||||
"reviewer": "Emma Davis"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 66,
|
|
||||||
"header": "Technical Debt Assessment",
|
|
||||||
"type": "Technical content",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "24",
|
|
||||||
"limit": "27",
|
|
||||||
"reviewer": "Eddie Lake"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 67,
|
|
||||||
"header": "Capacity Planning",
|
|
||||||
"type": "Planning",
|
|
||||||
"status": "In Process",
|
|
||||||
"target": "21",
|
|
||||||
"limit": "24",
|
|
||||||
"reviewer": "Jamik Tashpulatov"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 68,
|
|
||||||
"header": "Service Level Agreements",
|
|
||||||
"type": "Legal",
|
|
||||||
"status": "Done",
|
|
||||||
"target": "26",
|
|
||||||
"limit": "29",
|
|
||||||
"reviewer": "Assign reviewer"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import { cookies } from "next/headers"
|
|
||||||
|
|
||||||
import {
|
|
||||||
SidebarInset,
|
|
||||||
SidebarProvider,
|
|
||||||
} from "@/registry/new-york-v4/ui/sidebar"
|
|
||||||
import { AppSidebar } from "@/app/(examples)/dashboard/components/app-sidebar"
|
|
||||||
import { SiteHeader } from "@/app/(examples)/dashboard/components/site-header"
|
|
||||||
|
|
||||||
import "@/app/(examples)/dashboard/theme.css"
|
|
||||||
|
|
||||||
export default async function DashboardLayout({
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
children: React.ReactNode
|
|
||||||
}) {
|
|
||||||
const cookieStore = await cookies()
|
|
||||||
const defaultOpen = cookieStore.get("sidebar_state")?.value === "true"
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SidebarProvider
|
|
||||||
defaultOpen={defaultOpen}
|
|
||||||
style={
|
|
||||||
{
|
|
||||||
"--sidebar-width": "calc(var(--spacing) * 72)",
|
|
||||||
} as React.CSSProperties
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<AppSidebar variant="inset" />
|
|
||||||
<SidebarInset>
|
|
||||||
<SiteHeader />
|
|
||||||
<div className="flex flex-1 flex-col">{children}</div>
|
|
||||||
</SidebarInset>
|
|
||||||
</SidebarProvider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { ChartAreaInteractive } from "@/app/(examples)/dashboard/components/chart-area-interactive"
|
|
||||||
import { DataTable } from "@/app/(examples)/dashboard/components/data-table"
|
|
||||||
import { SectionCards } from "@/app/(examples)/dashboard/components/section-cards"
|
|
||||||
import data from "@/app/(examples)/dashboard/data.json"
|
|
||||||
|
|
||||||
export default function Page() {
|
|
||||||
return (
|
|
||||||
<div className="@container/main flex flex-1 flex-col gap-2">
|
|
||||||
<div className="flex flex-col gap-4 py-4 md:gap-6 md:py-6">
|
|
||||||
<SectionCards />
|
|
||||||
<div className="px-4 lg:px-6">
|
|
||||||
<ChartAreaInteractive />
|
|
||||||
</div>
|
|
||||||
<DataTable data={data} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
body {
|
|
||||||
@apply overscroll-none bg-transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--font-sans: var(--font-inter);
|
|
||||||
--header-height: calc(var(--spacing) * 12 + 1px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-scaled {
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
--radius: 0.6rem;
|
|
||||||
--text-lg: 1.05rem;
|
|
||||||
--text-base: 0.85rem;
|
|
||||||
--text-sm: 0.8rem;
|
|
||||||
--spacing: 0.222222rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="card"] {
|
|
||||||
--spacing: 0.16rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="select-trigger"],
|
|
||||||
[data-slot="toggle-group-item"] {
|
|
||||||
--spacing: 0.222222rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-default,
|
|
||||||
.theme-default-scaled {
|
|
||||||
--primary: var(--color-neutral-600);
|
|
||||||
--primary-foreground: var(--color-neutral-50);
|
|
||||||
|
|
||||||
@variant dark {
|
|
||||||
--primary: var(--color-neutral-500);
|
|
||||||
--primary-foreground: var(--color-neutral-50);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-blue,
|
|
||||||
.theme-blue-scaled {
|
|
||||||
--primary: var(--color-blue-600);
|
|
||||||
--primary-foreground: var(--color-blue-50);
|
|
||||||
|
|
||||||
@variant dark {
|
|
||||||
--primary: var(--color-blue-500);
|
|
||||||
--primary-foreground: var(--color-blue-50);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-green,
|
|
||||||
.theme-green-scaled {
|
|
||||||
--primary: var(--color-lime-600);
|
|
||||||
--primary-foreground: var(--color-lime-50);
|
|
||||||
|
|
||||||
@variant dark {
|
|
||||||
--primary: var(--color-lime-600);
|
|
||||||
--primary-foreground: var(--color-lime-50);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-amber,
|
|
||||||
.theme-amber-scaled {
|
|
||||||
--primary: var(--color-amber-600);
|
|
||||||
--primary-foreground: var(--color-amber-50);
|
|
||||||
|
|
||||||
@variant dark {
|
|
||||||
--primary: var(--color-amber-500);
|
|
||||||
--primary-foreground: var(--color-amber-50);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-mono,
|
|
||||||
.theme-mono-scaled {
|
|
||||||
--font-sans: var(--font-mono);
|
|
||||||
--primary: var(--color-neutral-600);
|
|
||||||
--primary-foreground: var(--color-neutral-50);
|
|
||||||
|
|
||||||
@variant dark {
|
|
||||||
--primary: var(--color-neutral-500);
|
|
||||||
--primary-foreground: var(--color-neutral-50);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rounded-xs,
|
|
||||||
.rounded-sm,
|
|
||||||
.rounded-md,
|
|
||||||
.rounded-lg,
|
|
||||||
.rounded-xl {
|
|
||||||
@apply !rounded-none;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shadow-xs,
|
|
||||||
.shadow-sm,
|
|
||||||
.shadow-md,
|
|
||||||
.shadow-lg,
|
|
||||||
.shadow-xl {
|
|
||||||
@apply !shadow-none;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="toggle-group"],
|
|
||||||
[data-slot="toggle-group-item"] {
|
|
||||||
@apply !rounded-none !shadow-none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
import * as React from "react"
|
|
||||||
import { Metadata } from "next"
|
|
||||||
import { notFound } from "next/navigation"
|
|
||||||
import { registryItemSchema } from "shadcn/registry"
|
|
||||||
import { z } from "zod"
|
|
||||||
|
|
||||||
import { getRegistryComponent, getRegistryItem } from "@/lib/registry"
|
|
||||||
import { absoluteUrl, cn } from "@/lib/utils"
|
|
||||||
import { siteConfig } from "@/www/config/site"
|
|
||||||
|
|
||||||
const getCachedRegistryItem = React.cache(async (name: string) => {
|
|
||||||
return await getRegistryItem(name)
|
|
||||||
})
|
|
||||||
|
|
||||||
export async function generateMetadata({
|
|
||||||
params,
|
|
||||||
}: {
|
|
||||||
params: Promise<{
|
|
||||||
name: string
|
|
||||||
}>
|
|
||||||
}): Promise<Metadata> {
|
|
||||||
const { name } = await params
|
|
||||||
const item = await getCachedRegistryItem(name)
|
|
||||||
|
|
||||||
if (!item) {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const title = item.name
|
|
||||||
const description = item.description
|
|
||||||
|
|
||||||
return {
|
|
||||||
title: `${item.name}${item.description ? ` - ${item.description}` : ""}`,
|
|
||||||
description,
|
|
||||||
openGraph: {
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
type: "article",
|
|
||||||
url: absoluteUrl(`/blocks/${item.name}`),
|
|
||||||
images: [
|
|
||||||
{
|
|
||||||
url: siteConfig.ogImage,
|
|
||||||
width: 1200,
|
|
||||||
height: 630,
|
|
||||||
alt: siteConfig.name,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
twitter: {
|
|
||||||
card: "summary_large_image",
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
images: [siteConfig.ogImage],
|
|
||||||
creator: "@shadcn",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const dynamicParams = false
|
|
||||||
|
|
||||||
export async function generateStaticParams() {
|
|
||||||
const { Index } = await import("@/__registry__")
|
|
||||||
const index = z.record(registryItemSchema).parse(Index)
|
|
||||||
|
|
||||||
return Object.values(index)
|
|
||||||
.filter((block) =>
|
|
||||||
["registry:block", "registry:component"].includes(block.type)
|
|
||||||
)
|
|
||||||
.map((block) => ({
|
|
||||||
name: block.name,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function BlockPage({
|
|
||||||
params,
|
|
||||||
}: {
|
|
||||||
params: Promise<{
|
|
||||||
name: string
|
|
||||||
}>
|
|
||||||
}) {
|
|
||||||
const { name } = await params
|
|
||||||
const item = await getCachedRegistryItem(name)
|
|
||||||
const Component = getRegistryComponent(name)
|
|
||||||
|
|
||||||
if (!item || !Component) {
|
|
||||||
return notFound()
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className={cn("themes-wrapper bg-background", item.meta?.container)}>
|
|
||||||
<Component />
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
@import "tailwindcss";
|
|
||||||
|
|
||||||
@import "tw-animate-css";
|
|
||||||
|
|
||||||
@custom-variant dark (&:is(.dark *));
|
|
||||||
|
|
||||||
@import "./themes.css";
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--radius: 0.625rem;
|
|
||||||
--background: oklch(1 0 0);
|
|
||||||
--foreground: oklch(0.145 0 0);
|
|
||||||
--card: oklch(1 0 0);
|
|
||||||
--card-foreground: oklch(0.145 0 0);
|
|
||||||
--popover: oklch(1 0 0);
|
|
||||||
--popover-foreground: oklch(0.145 0 0);
|
|
||||||
--primary: oklch(0.205 0 0);
|
|
||||||
--primary-foreground: oklch(0.985 0 0);
|
|
||||||
--secondary: oklch(0.97 0 0);
|
|
||||||
--secondary-foreground: oklch(0.205 0 0);
|
|
||||||
--muted: oklch(0.97 0 0);
|
|
||||||
--muted-foreground: oklch(0.556 0 0);
|
|
||||||
--accent: oklch(0.97 0 0);
|
|
||||||
--accent-foreground: oklch(0.205 0 0);
|
|
||||||
--destructive: oklch(0.577 0.245 27.325);
|
|
||||||
--border: oklch(0.922 0 0);
|
|
||||||
--input: oklch(0.922 0 0);
|
|
||||||
--ring: oklch(0.708 0 0);
|
|
||||||
--chart-1: oklch(0.646 0.222 41.116);
|
|
||||||
--chart-2: oklch(0.6 0.118 184.704);
|
|
||||||
--chart-3: oklch(0.398 0.07 227.392);
|
|
||||||
--chart-4: oklch(0.828 0.189 84.429);
|
|
||||||
--chart-5: oklch(0.769 0.188 70.08);
|
|
||||||
--sidebar: oklch(0.985 0 0);
|
|
||||||
--sidebar-foreground: oklch(0.145 0 0);
|
|
||||||
--sidebar-primary: oklch(0.205 0 0);
|
|
||||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-accent: oklch(0.97 0 0);
|
|
||||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
|
||||||
--sidebar-border: oklch(0.922 0 0);
|
|
||||||
--sidebar-ring: oklch(0.708 0 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark {
|
|
||||||
--background: oklch(0.145 0 0);
|
|
||||||
--foreground: oklch(0.985 0 0);
|
|
||||||
--card: oklch(0.205 0 0);
|
|
||||||
--card-foreground: oklch(0.985 0 0);
|
|
||||||
--popover: oklch(0.269 0 0);
|
|
||||||
--popover-foreground: oklch(0.985 0 0);
|
|
||||||
--primary: oklch(0.922 0 0);
|
|
||||||
--primary-foreground: oklch(0.205 0 0);
|
|
||||||
--secondary: oklch(0.269 0 0);
|
|
||||||
--secondary-foreground: oklch(0.985 0 0);
|
|
||||||
--muted: oklch(0.269 0 0);
|
|
||||||
--muted-foreground: oklch(0.708 0 0);
|
|
||||||
--accent: oklch(0.371 0 0);
|
|
||||||
--accent-foreground: oklch(0.985 0 0);
|
|
||||||
--destructive: oklch(0.704 0.191 22.216);
|
|
||||||
--border: oklch(1 0 0 / 10%);
|
|
||||||
--input: oklch(1 0 0 / 15%);
|
|
||||||
--ring: oklch(0.556 0 0);
|
|
||||||
--chart-1: oklch(0.488 0.243 264.376);
|
|
||||||
--chart-2: oklch(0.696 0.17 162.48);
|
|
||||||
--chart-3: oklch(0.769 0.188 70.08);
|
|
||||||
--chart-4: oklch(0.627 0.265 303.9);
|
|
||||||
--chart-5: oklch(0.645 0.246 16.439);
|
|
||||||
--sidebar: oklch(0.205 0 0);
|
|
||||||
--sidebar-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
||||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-accent: oklch(0.269 0 0);
|
|
||||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-border: oklch(1 0 0 / 10%);
|
|
||||||
--sidebar-ring: oklch(0.439 0 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@theme inline {
|
|
||||||
--radius-sm: calc(var(--radius) - 4px);
|
|
||||||
--radius-md: calc(var(--radius) - 2px);
|
|
||||||
--radius-lg: var(--radius);
|
|
||||||
--radius-xl: calc(var(--radius) + 4px);
|
|
||||||
--color-background: var(--background);
|
|
||||||
--color-foreground: var(--foreground);
|
|
||||||
--color-card: var(--card);
|
|
||||||
--color-card-foreground: var(--card-foreground);
|
|
||||||
--color-popover: var(--popover);
|
|
||||||
--color-popover-foreground: var(--popover-foreground);
|
|
||||||
--color-primary: var(--primary);
|
|
||||||
--color-primary-foreground: var(--primary-foreground);
|
|
||||||
--color-secondary: var(--secondary);
|
|
||||||
--color-secondary-foreground: var(--secondary-foreground);
|
|
||||||
--color-muted: var(--muted);
|
|
||||||
--color-muted-foreground: var(--muted-foreground);
|
|
||||||
--color-accent: var(--accent);
|
|
||||||
--color-accent-foreground: var(--accent-foreground);
|
|
||||||
--color-destructive: var(--destructive);
|
|
||||||
--color-border: var(--border);
|
|
||||||
--color-input: var(--input);
|
|
||||||
--color-ring: var(--ring);
|
|
||||||
--color-chart-1: var(--chart-1);
|
|
||||||
--color-chart-2: var(--chart-2);
|
|
||||||
--color-chart-3: var(--chart-3);
|
|
||||||
--color-chart-4: var(--chart-4);
|
|
||||||
--color-chart-5: var(--chart-5);
|
|
||||||
--color-sidebar: var(--sidebar);
|
|
||||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
||||||
--color-sidebar-primary: var(--sidebar-primary);
|
|
||||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
||||||
--color-sidebar-accent: var(--sidebar-accent);
|
|
||||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
||||||
--color-sidebar-border: var(--sidebar-border);
|
|
||||||
--color-sidebar-ring: var(--sidebar-ring);
|
|
||||||
}
|
|
||||||
|
|
||||||
@layer base {
|
|
||||||
* {
|
|
||||||
@apply border-border outline-ring/50;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
@apply bg-background text-foreground;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
import type { Metadata, Viewport } from "next"
|
|
||||||
import { cookies } from "next/headers"
|
|
||||||
|
|
||||||
import { fontVariables } from "@/lib/fonts"
|
|
||||||
import { Analytics } from "@/components/analytics"
|
|
||||||
import { ThemeProvider } from "@/components/theme-provider"
|
|
||||||
import { Toaster } from "@/registry/new-york-v4/ui/sonner"
|
|
||||||
import { siteConfig } from "@/www/config/site"
|
|
||||||
|
|
||||||
import "./globals.css"
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
import { ActiveThemeProvider } from "@/components/active-theme"
|
|
||||||
|
|
||||||
const META_THEME_COLORS = {
|
|
||||||
light: "#ffffff",
|
|
||||||
dark: "#09090b",
|
|
||||||
}
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
|
||||||
title: {
|
|
||||||
default: siteConfig.name,
|
|
||||||
template: `%s - ${siteConfig.name}`,
|
|
||||||
},
|
|
||||||
metadataBase: new URL("https://v4.shadcn.com"),
|
|
||||||
description: siteConfig.description,
|
|
||||||
keywords: [
|
|
||||||
"Next.js",
|
|
||||||
"React",
|
|
||||||
"Tailwind CSS",
|
|
||||||
"Server Components",
|
|
||||||
"Radix UI",
|
|
||||||
],
|
|
||||||
authors: [
|
|
||||||
{
|
|
||||||
name: "shadcn",
|
|
||||||
url: "https://shadcn.com",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
creator: "shadcn",
|
|
||||||
openGraph: {
|
|
||||||
type: "website",
|
|
||||||
locale: "en_US",
|
|
||||||
url: "https://v4.shadcn.com",
|
|
||||||
title: siteConfig.name,
|
|
||||||
description: siteConfig.description,
|
|
||||||
siteName: siteConfig.name,
|
|
||||||
images: [
|
|
||||||
{
|
|
||||||
url: "https://v4.shadcn.com/opengraph-image.png",
|
|
||||||
width: 1200,
|
|
||||||
height: 630,
|
|
||||||
alt: siteConfig.name,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
twitter: {
|
|
||||||
card: "summary_large_image",
|
|
||||||
title: siteConfig.name,
|
|
||||||
description: siteConfig.description,
|
|
||||||
images: ["https://v4.shadcn.com/opengraph-image.png"],
|
|
||||||
creator: "@shadcn",
|
|
||||||
},
|
|
||||||
icons: {
|
|
||||||
icon: "/favicon.ico",
|
|
||||||
shortcut: "/favicon-16x16.png",
|
|
||||||
apple: "/apple-touch-icon.png",
|
|
||||||
},
|
|
||||||
manifest: `${siteConfig.url}/site.webmanifest`,
|
|
||||||
}
|
|
||||||
|
|
||||||
export const viewport: Viewport = {
|
|
||||||
themeColor: META_THEME_COLORS.light,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function RootLayout({
|
|
||||||
children,
|
|
||||||
}: Readonly<{
|
|
||||||
children: React.ReactNode
|
|
||||||
}>) {
|
|
||||||
const cookieStore = await cookies()
|
|
||||||
const activeThemeValue = cookieStore.get("active_theme")?.value
|
|
||||||
const isScaled = activeThemeValue?.endsWith("-scaled")
|
|
||||||
|
|
||||||
return (
|
|
||||||
<html lang="en" suppressHydrationWarning>
|
|
||||||
<head>
|
|
||||||
<script
|
|
||||||
dangerouslySetInnerHTML={{
|
|
||||||
__html: `
|
|
||||||
try {
|
|
||||||
if (localStorage.theme === 'dark' || ((!('theme' in localStorage) || localStorage.theme === 'system') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
|
||||||
document.querySelector('meta[name="theme-color"]').setAttribute('content', '${META_THEME_COLORS.dark}')
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</head>
|
|
||||||
<body
|
|
||||||
className={cn(
|
|
||||||
"bg-background overscroll-none font-sans antialiased",
|
|
||||||
activeThemeValue ? `theme-${activeThemeValue}` : "",
|
|
||||||
isScaled ? "theme-scaled" : "",
|
|
||||||
fontVariables
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<ThemeProvider
|
|
||||||
attribute="class"
|
|
||||||
defaultTheme="system"
|
|
||||||
enableSystem
|
|
||||||
disableTransitionOnChange
|
|
||||||
enableColorScheme
|
|
||||||
>
|
|
||||||
<ActiveThemeProvider initialTheme={activeThemeValue}>
|
|
||||||
{children}
|
|
||||||
<Toaster />
|
|
||||||
<Analytics />
|
|
||||||
</ActiveThemeProvider>
|
|
||||||
</ThemeProvider>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,364 +0,0 @@
|
|||||||
.theme-stone {
|
|
||||||
--radius: 0.625rem;
|
|
||||||
--background: oklch(1 0 0);
|
|
||||||
--foreground: oklch(0.147 0.004 49.25);
|
|
||||||
--card: oklch(1 0 0);
|
|
||||||
--card-foreground: oklch(0.147 0.004 49.25);
|
|
||||||
--popover: oklch(1 0 0);
|
|
||||||
--popover-foreground: oklch(0.147 0.004 49.25);
|
|
||||||
--primary: oklch(0.216 0.006 56.043);
|
|
||||||
--primary-foreground: oklch(0.985 0.001 106.423);
|
|
||||||
--secondary: oklch(0.97 0.001 106.424);
|
|
||||||
--secondary-foreground: oklch(0.216 0.006 56.043);
|
|
||||||
--muted: oklch(0.97 0.001 106.424);
|
|
||||||
--muted-foreground: oklch(0.553 0.013 58.071);
|
|
||||||
--accent: oklch(0.97 0.001 106.424);
|
|
||||||
--accent-foreground: oklch(0.216 0.006 56.043);
|
|
||||||
--destructive: oklch(0.577 0.245 27.325);
|
|
||||||
--border: oklch(0.923 0.003 48.717);
|
|
||||||
--input: oklch(0.923 0.003 48.717);
|
|
||||||
--ring: oklch(0.709 0.01 56.259);
|
|
||||||
--chart-1: oklch(0.646 0.222 41.116);
|
|
||||||
--chart-2: oklch(0.6 0.118 184.704);
|
|
||||||
--chart-3: oklch(0.398 0.07 227.392);
|
|
||||||
--chart-4: oklch(0.828 0.189 84.429);
|
|
||||||
--chart-5: oklch(0.769 0.188 70.08);
|
|
||||||
--sidebar: oklch(0.985 0.001 106.423);
|
|
||||||
--sidebar-foreground: oklch(0.147 0.004 49.25);
|
|
||||||
--sidebar-primary: oklch(0.216 0.006 56.043);
|
|
||||||
--sidebar-primary-foreground: oklch(0.985 0.001 106.423);
|
|
||||||
--sidebar-accent: oklch(0.97 0.001 106.424);
|
|
||||||
--sidebar-accent-foreground: oklch(0.216 0.006 56.043);
|
|
||||||
--sidebar-border: oklch(0.923 0.003 48.717);
|
|
||||||
--sidebar-ring: oklch(0.709 0.01 56.259);
|
|
||||||
|
|
||||||
@variant dark {
|
|
||||||
--background: oklch(0.147 0.004 49.25);
|
|
||||||
--foreground: oklch(0.985 0.001 106.423);
|
|
||||||
--card: oklch(0.216 0.006 56.043);
|
|
||||||
--card-foreground: oklch(0.985 0.001 106.423);
|
|
||||||
--popover: oklch(0.216 0.006 56.043);
|
|
||||||
--popover-foreground: oklch(0.985 0.001 106.423);
|
|
||||||
--primary: oklch(0.923 0.003 48.717);
|
|
||||||
--primary-foreground: oklch(0.216 0.006 56.043);
|
|
||||||
--secondary: oklch(0.268 0.007 34.298);
|
|
||||||
--secondary-foreground: oklch(0.985 0.001 106.423);
|
|
||||||
--muted: oklch(0.268 0.007 34.298);
|
|
||||||
--muted-foreground: oklch(0.709 0.01 56.259);
|
|
||||||
--accent: oklch(0.268 0.007 34.298);
|
|
||||||
--accent-foreground: oklch(0.985 0.001 106.423);
|
|
||||||
--destructive: oklch(0.704 0.191 22.216);
|
|
||||||
--border: oklch(1 0 0 / 10%);
|
|
||||||
--input: oklch(1 0 0 / 15%);
|
|
||||||
--ring: oklch(0.553 0.013 58.071);
|
|
||||||
--chart-1: oklch(0.488 0.243 264.376);
|
|
||||||
--chart-2: oklch(0.696 0.17 162.48);
|
|
||||||
--chart-3: oklch(0.769 0.188 70.08);
|
|
||||||
--chart-4: oklch(0.627 0.265 303.9);
|
|
||||||
--chart-5: oklch(0.645 0.246 16.439);
|
|
||||||
--sidebar: oklch(0.216 0.006 56.043);
|
|
||||||
--sidebar-foreground: oklch(0.985 0.001 106.423);
|
|
||||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
||||||
--sidebar-primary-foreground: oklch(0.985 0.001 106.423);
|
|
||||||
--sidebar-accent: oklch(0.268 0.007 34.298);
|
|
||||||
--sidebar-accent-foreground: oklch(0.985 0.001 106.423);
|
|
||||||
--sidebar-border: oklch(1 0 0 / 10%);
|
|
||||||
--sidebar-ring: oklch(0.553 0.013 58.071);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-zinc {
|
|
||||||
--radius: 0.625rem;
|
|
||||||
--background: oklch(1 0 0);
|
|
||||||
--foreground: oklch(0.141 0.005 285.823);
|
|
||||||
--card: oklch(1 0 0);
|
|
||||||
--card-foreground: oklch(0.141 0.005 285.823);
|
|
||||||
--popover: oklch(1 0 0);
|
|
||||||
--popover-foreground: oklch(0.141 0.005 285.823);
|
|
||||||
--primary: oklch(0.21 0.006 285.885);
|
|
||||||
--primary-foreground: oklch(0.985 0 0);
|
|
||||||
--secondary: oklch(0.967 0.001 286.375);
|
|
||||||
--secondary-foreground: oklch(0.21 0.006 285.885);
|
|
||||||
--muted: oklch(0.967 0.001 286.375);
|
|
||||||
--muted-foreground: oklch(0.552 0.016 285.938);
|
|
||||||
--accent: oklch(0.967 0.001 286.375);
|
|
||||||
--accent-foreground: oklch(0.21 0.006 285.885);
|
|
||||||
--destructive: oklch(0.577 0.245 27.325);
|
|
||||||
--border: oklch(0.92 0.004 286.32);
|
|
||||||
--input: oklch(0.92 0.004 286.32);
|
|
||||||
--ring: oklch(0.705 0.015 286.067);
|
|
||||||
--chart-1: oklch(0.646 0.222 41.116);
|
|
||||||
--chart-2: oklch(0.6 0.118 184.704);
|
|
||||||
--chart-3: oklch(0.398 0.07 227.392);
|
|
||||||
--chart-4: oklch(0.828 0.189 84.429);
|
|
||||||
--chart-5: oklch(0.769 0.188 70.08);
|
|
||||||
--sidebar: oklch(0.985 0 0);
|
|
||||||
--sidebar-foreground: oklch(0.141 0.005 285.823);
|
|
||||||
--sidebar-primary: oklch(0.21 0.006 285.885);
|
|
||||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-accent: oklch(0.967 0.001 286.375);
|
|
||||||
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
|
|
||||||
--sidebar-border: oklch(0.92 0.004 286.32);
|
|
||||||
--sidebar-ring: oklch(0.705 0.015 286.067);
|
|
||||||
|
|
||||||
@variant dark {
|
|
||||||
--background: oklch(0.141 0.005 285.823);
|
|
||||||
--foreground: oklch(0.985 0 0);
|
|
||||||
--card: oklch(0.21 0.006 285.885);
|
|
||||||
--card-foreground: oklch(0.985 0 0);
|
|
||||||
--popover: oklch(0.21 0.006 285.885);
|
|
||||||
--popover-foreground: oklch(0.985 0 0);
|
|
||||||
--primary: oklch(0.92 0.004 286.32);
|
|
||||||
--primary-foreground: oklch(0.21 0.006 285.885);
|
|
||||||
--secondary: oklch(0.274 0.006 286.033);
|
|
||||||
--secondary-foreground: oklch(0.985 0 0);
|
|
||||||
--muted: oklch(0.274 0.006 286.033);
|
|
||||||
--muted-foreground: oklch(0.705 0.015 286.067);
|
|
||||||
--accent: oklch(0.274 0.006 286.033);
|
|
||||||
--accent-foreground: oklch(0.985 0 0);
|
|
||||||
--destructive: oklch(0.704 0.191 22.216);
|
|
||||||
--border: oklch(1 0 0 / 10%);
|
|
||||||
--input: oklch(1 0 0 / 15%);
|
|
||||||
--ring: oklch(0.552 0.016 285.938);
|
|
||||||
--chart-1: oklch(0.488 0.243 264.376);
|
|
||||||
--chart-2: oklch(0.696 0.17 162.48);
|
|
||||||
--chart-3: oklch(0.769 0.188 70.08);
|
|
||||||
--chart-4: oklch(0.627 0.265 303.9);
|
|
||||||
--chart-5: oklch(0.645 0.246 16.439);
|
|
||||||
--sidebar: oklch(0.21 0.006 285.885);
|
|
||||||
--sidebar-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
||||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-accent: oklch(0.274 0.006 286.033);
|
|
||||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-border: oklch(1 0 0 / 10%);
|
|
||||||
--sidebar-ring: oklch(0.552 0.016 285.938);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-neutral {
|
|
||||||
--radius: 0.625rem;
|
|
||||||
--background: oklch(1 0 0);
|
|
||||||
--foreground: oklch(0.145 0 0);
|
|
||||||
--card: oklch(1 0 0);
|
|
||||||
--card-foreground: oklch(0.145 0 0);
|
|
||||||
--popover: oklch(1 0 0);
|
|
||||||
--popover-foreground: oklch(0.145 0 0);
|
|
||||||
--primary: oklch(0.205 0 0);
|
|
||||||
--primary-foreground: oklch(0.985 0 0);
|
|
||||||
--secondary: oklch(0.97 0 0);
|
|
||||||
--secondary-foreground: oklch(0.205 0 0);
|
|
||||||
--muted: oklch(0.97 0 0);
|
|
||||||
--muted-foreground: oklch(0.556 0 0);
|
|
||||||
--accent: oklch(0.97 0 0);
|
|
||||||
--accent-foreground: oklch(0.205 0 0);
|
|
||||||
--destructive: oklch(0.577 0.245 27.325);
|
|
||||||
--border: oklch(0.922 0 0);
|
|
||||||
--input: oklch(0.922 0 0);
|
|
||||||
--ring: oklch(0.708 0 0);
|
|
||||||
--chart-1: oklch(0.646 0.222 41.116);
|
|
||||||
--chart-2: oklch(0.6 0.118 184.704);
|
|
||||||
--chart-3: oklch(0.398 0.07 227.392);
|
|
||||||
--chart-4: oklch(0.828 0.189 84.429);
|
|
||||||
--chart-5: oklch(0.769 0.188 70.08);
|
|
||||||
--sidebar: oklch(0.985 0 0);
|
|
||||||
--sidebar-foreground: oklch(0.145 0 0);
|
|
||||||
--sidebar-primary: oklch(0.205 0 0);
|
|
||||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-accent: oklch(0.97 0 0);
|
|
||||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
|
||||||
--sidebar-border: oklch(0.922 0 0);
|
|
||||||
--sidebar-ring: oklch(0.708 0 0);
|
|
||||||
|
|
||||||
@variant dark {
|
|
||||||
--background: oklch(0.145 0 0);
|
|
||||||
--foreground: oklch(0.985 0 0);
|
|
||||||
--card: oklch(0.205 0 0);
|
|
||||||
--card-foreground: oklch(0.985 0 0);
|
|
||||||
--popover: oklch(0.205 0 0);
|
|
||||||
--popover-foreground: oklch(0.985 0 0);
|
|
||||||
--primary: oklch(0.922 0 0);
|
|
||||||
--primary-foreground: oklch(0.205 0 0);
|
|
||||||
--secondary: oklch(0.269 0 0);
|
|
||||||
--secondary-foreground: oklch(0.985 0 0);
|
|
||||||
--muted: oklch(0.269 0 0);
|
|
||||||
--muted-foreground: oklch(0.708 0 0);
|
|
||||||
--accent: oklch(0.269 0 0);
|
|
||||||
--accent-foreground: oklch(0.985 0 0);
|
|
||||||
--destructive: oklch(0.704 0.191 22.216);
|
|
||||||
--border: oklch(1 0 0 / 10%);
|
|
||||||
--input: oklch(1 0 0 / 15%);
|
|
||||||
--ring: oklch(0.556 0 0);
|
|
||||||
--chart-1: oklch(0.488 0.243 264.376);
|
|
||||||
--chart-2: oklch(0.696 0.17 162.48);
|
|
||||||
--chart-3: oklch(0.769 0.188 70.08);
|
|
||||||
--chart-4: oklch(0.627 0.265 303.9);
|
|
||||||
--chart-5: oklch(0.645 0.246 16.439);
|
|
||||||
--sidebar: oklch(0.205 0 0);
|
|
||||||
--sidebar-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
||||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-accent: oklch(0.269 0 0);
|
|
||||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-border: oklch(1 0 0 / 10%);
|
|
||||||
--sidebar-ring: oklch(0.556 0 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-gray {
|
|
||||||
--radius: 0.625rem;
|
|
||||||
--background: oklch(1 0 0);
|
|
||||||
--foreground: oklch(0.13 0.028 261.692);
|
|
||||||
--card: oklch(1 0 0);
|
|
||||||
--card-foreground: oklch(0.13 0.028 261.692);
|
|
||||||
--popover: oklch(1 0 0);
|
|
||||||
--popover-foreground: oklch(0.13 0.028 261.692);
|
|
||||||
--primary: oklch(0.21 0.034 264.665);
|
|
||||||
--primary-foreground: oklch(0.985 0.002 247.839);
|
|
||||||
--secondary: oklch(0.967 0.003 264.542);
|
|
||||||
--secondary-foreground: oklch(0.21 0.034 264.665);
|
|
||||||
--muted: oklch(0.967 0.003 264.542);
|
|
||||||
--muted-foreground: oklch(0.551 0.027 264.364);
|
|
||||||
--accent: oklch(0.967 0.003 264.542);
|
|
||||||
--accent-foreground: oklch(0.21 0.034 264.665);
|
|
||||||
--destructive: oklch(0.577 0.245 27.325);
|
|
||||||
--border: oklch(0.928 0.006 264.531);
|
|
||||||
--input: oklch(0.928 0.006 264.531);
|
|
||||||
--ring: oklch(0.707 0.022 261.325);
|
|
||||||
--chart-1: oklch(0.646 0.222 41.116);
|
|
||||||
--chart-2: oklch(0.6 0.118 184.704);
|
|
||||||
--chart-3: oklch(0.398 0.07 227.392);
|
|
||||||
--chart-4: oklch(0.828 0.189 84.429);
|
|
||||||
--chart-5: oklch(0.769 0.188 70.08);
|
|
||||||
--sidebar: oklch(0.985 0.002 247.839);
|
|
||||||
--sidebar-foreground: oklch(0.13 0.028 261.692);
|
|
||||||
--sidebar-primary: oklch(0.21 0.034 264.665);
|
|
||||||
--sidebar-primary-foreground: oklch(0.985 0.002 247.839);
|
|
||||||
--sidebar-accent: oklch(0.967 0.003 264.542);
|
|
||||||
--sidebar-accent-foreground: oklch(0.21 0.034 264.665);
|
|
||||||
--sidebar-border: oklch(0.928 0.006 264.531);
|
|
||||||
--sidebar-ring: oklch(0.707 0.022 261.325);
|
|
||||||
|
|
||||||
@variant dark {
|
|
||||||
--background: oklch(0.13 0.028 261.692);
|
|
||||||
--foreground: oklch(0.985 0.002 247.839);
|
|
||||||
--card: oklch(0.21 0.034 264.665);
|
|
||||||
--card-foreground: oklch(0.985 0.002 247.839);
|
|
||||||
--popover: oklch(0.21 0.034 264.665);
|
|
||||||
--popover-foreground: oklch(0.985 0.002 247.839);
|
|
||||||
--primary: oklch(0.928 0.006 264.531);
|
|
||||||
--primary-foreground: oklch(0.21 0.034 264.665);
|
|
||||||
--secondary: oklch(0.278 0.033 256.848);
|
|
||||||
--secondary-foreground: oklch(0.985 0.002 247.839);
|
|
||||||
--muted: oklch(0.278 0.033 256.848);
|
|
||||||
--muted-foreground: oklch(0.707 0.022 261.325);
|
|
||||||
--accent: oklch(0.278 0.033 256.848);
|
|
||||||
--accent-foreground: oklch(0.985 0.002 247.839);
|
|
||||||
--destructive: oklch(0.704 0.191 22.216);
|
|
||||||
--border: oklch(1 0 0 / 10%);
|
|
||||||
--input: oklch(1 0 0 / 15%);
|
|
||||||
--ring: oklch(0.551 0.027 264.364);
|
|
||||||
--chart-1: oklch(0.488 0.243 264.376);
|
|
||||||
--chart-2: oklch(0.696 0.17 162.48);
|
|
||||||
--chart-3: oklch(0.769 0.188 70.08);
|
|
||||||
--chart-4: oklch(0.627 0.265 303.9);
|
|
||||||
--chart-5: oklch(0.645 0.246 16.439);
|
|
||||||
--sidebar: oklch(0.21 0.034 264.665);
|
|
||||||
--sidebar-foreground: oklch(0.985 0.002 247.839);
|
|
||||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
||||||
--sidebar-primary-foreground: oklch(0.985 0.002 247.839);
|
|
||||||
--sidebar-accent: oklch(0.278 0.033 256.848);
|
|
||||||
--sidebar-accent-foreground: oklch(0.985 0.002 247.839);
|
|
||||||
--sidebar-border: oklch(1 0 0 / 10%);
|
|
||||||
--sidebar-ring: oklch(0.551 0.027 264.364);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-slate {
|
|
||||||
--radius: 0.625rem;
|
|
||||||
--background: oklch(1 0 0);
|
|
||||||
--foreground: oklch(0.129 0.042 264.695);
|
|
||||||
--card: oklch(1 0 0);
|
|
||||||
--card-foreground: oklch(0.129 0.042 264.695);
|
|
||||||
--popover: oklch(1 0 0);
|
|
||||||
--popover-foreground: oklch(0.129 0.042 264.695);
|
|
||||||
--primary: oklch(0.208 0.042 265.755);
|
|
||||||
--primary-foreground: oklch(0.984 0.003 247.858);
|
|
||||||
--secondary: oklch(0.968 0.007 247.896);
|
|
||||||
--secondary-foreground: oklch(0.208 0.042 265.755);
|
|
||||||
--muted: oklch(0.968 0.007 247.896);
|
|
||||||
--muted-foreground: oklch(0.554 0.046 257.417);
|
|
||||||
--accent: oklch(0.968 0.007 247.896);
|
|
||||||
--accent-foreground: oklch(0.208 0.042 265.755);
|
|
||||||
--destructive: oklch(0.577 0.245 27.325);
|
|
||||||
--border: oklch(0.929 0.013 255.508);
|
|
||||||
--input: oklch(0.929 0.013 255.508);
|
|
||||||
--ring: oklch(0.704 0.04 256.788);
|
|
||||||
--chart-1: oklch(0.646 0.222 41.116);
|
|
||||||
--chart-2: oklch(0.6 0.118 184.704);
|
|
||||||
--chart-3: oklch(0.398 0.07 227.392);
|
|
||||||
--chart-4: oklch(0.828 0.189 84.429);
|
|
||||||
--chart-5: oklch(0.769 0.188 70.08);
|
|
||||||
--sidebar: oklch(0.984 0.003 247.858);
|
|
||||||
--sidebar-foreground: oklch(0.129 0.042 264.695);
|
|
||||||
--sidebar-primary: oklch(0.208 0.042 265.755);
|
|
||||||
--sidebar-primary-foreground: oklch(0.984 0.003 247.858);
|
|
||||||
--sidebar-accent: oklch(0.968 0.007 247.896);
|
|
||||||
--sidebar-accent-foreground: oklch(0.208 0.042 265.755);
|
|
||||||
--sidebar-border: oklch(0.929 0.013 255.508);
|
|
||||||
--sidebar-ring: oklch(0.704 0.04 256.788);
|
|
||||||
|
|
||||||
@variant dark {
|
|
||||||
--background: oklch(0.129 0.042 264.695);
|
|
||||||
--foreground: oklch(0.984 0.003 247.858);
|
|
||||||
--card: oklch(0.208 0.042 265.755);
|
|
||||||
--card-foreground: oklch(0.984 0.003 247.858);
|
|
||||||
--popover: oklch(0.208 0.042 265.755);
|
|
||||||
--popover-foreground: oklch(0.984 0.003 247.858);
|
|
||||||
--primary: oklch(0.929 0.013 255.508);
|
|
||||||
--primary-foreground: oklch(0.208 0.042 265.755);
|
|
||||||
--secondary: oklch(0.279 0.041 260.031);
|
|
||||||
--secondary-foreground: oklch(0.984 0.003 247.858);
|
|
||||||
--muted: oklch(0.279 0.041 260.031);
|
|
||||||
--muted-foreground: oklch(0.704 0.04 256.788);
|
|
||||||
--accent: oklch(0.279 0.041 260.031);
|
|
||||||
--accent-foreground: oklch(0.984 0.003 247.858);
|
|
||||||
--destructive: oklch(0.704 0.191 22.216);
|
|
||||||
--border: oklch(1 0 0 / 10%);
|
|
||||||
--input: oklch(1 0 0 / 15%);
|
|
||||||
--ring: oklch(0.551 0.027 264.364);
|
|
||||||
--chart-1: oklch(0.488 0.243 264.376);
|
|
||||||
--chart-2: oklch(0.696 0.17 162.48);
|
|
||||||
--chart-3: oklch(0.769 0.188 70.08);
|
|
||||||
--chart-4: oklch(0.627 0.265 303.9);
|
|
||||||
--chart-5: oklch(0.645 0.246 16.439);
|
|
||||||
--sidebar: oklch(0.208 0.042 265.755);
|
|
||||||
--sidebar-foreground: oklch(0.984 0.003 247.858);
|
|
||||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
||||||
--sidebar-primary-foreground: oklch(0.984 0.003 247.858);
|
|
||||||
--sidebar-accent: oklch(0.279 0.041 260.031);
|
|
||||||
--sidebar-accent-foreground: oklch(0.984 0.003 247.858);
|
|
||||||
--sidebar-border: oklch(1 0 0 / 10%);
|
|
||||||
--sidebar-ring: oklch(0.551 0.027 264.364);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-scaled {
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
--radius: 0.6rem;
|
|
||||||
--text-lg: 1.05rem;
|
|
||||||
--text-base: 0.85rem;
|
|
||||||
--text-sm: 0.8rem;
|
|
||||||
--spacing: 0.222222rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="card"] {
|
|
||||||
--spacing: 0.16rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-slot="card-header"] *,
|
|
||||||
[data-slot="card-content"] *,
|
|
||||||
[data-slot="card-footer"] * {
|
|
||||||
--spacing: 0.222222rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://ui.shadcn.com/schema.json",
|
|
||||||
"style": "new-york",
|
|
||||||
"rsc": true,
|
|
||||||
"tsx": true,
|
|
||||||
"tailwind": {
|
|
||||||
"config": "",
|
|
||||||
"css": "app/globals.css",
|
|
||||||
"baseColor": "neutral",
|
|
||||||
"cssVariables": true,
|
|
||||||
"prefix": ""
|
|
||||||
},
|
|
||||||
"aliases": {
|
|
||||||
"components": "@/components",
|
|
||||||
"utils": "@/lib/utils",
|
|
||||||
"ui": "@/registry/new-york-v4/ui",
|
|
||||||
"lib": "@/lib",
|
|
||||||
"hooks": "@/hooks"
|
|
||||||
},
|
|
||||||
"iconLibrary": "lucide"
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
import {
|
|
||||||
Accordion,
|
|
||||||
AccordionContent,
|
|
||||||
AccordionItem,
|
|
||||||
AccordionTrigger,
|
|
||||||
} from "@/registry/new-york-v4/ui/accordion"
|
|
||||||
|
|
||||||
export function AccordionDemo() {
|
|
||||||
return (
|
|
||||||
<div className="grid w-full max-w-xl gap-4">
|
|
||||||
<Accordion type="single" collapsible className="w-full">
|
|
||||||
<AccordionItem value="item-1">
|
|
||||||
<AccordionTrigger>Is it accessible?</AccordionTrigger>
|
|
||||||
<AccordionContent>
|
|
||||||
Yes. It adheres to the WAI-ARIA design pattern.
|
|
||||||
</AccordionContent>
|
|
||||||
</AccordionItem>
|
|
||||||
<AccordionItem value="item-2">
|
|
||||||
<AccordionTrigger>Is it styled?</AccordionTrigger>
|
|
||||||
<AccordionContent>
|
|
||||||
Yes. It comes with default styles that matches the other
|
|
||||||
components' aesthetic.
|
|
||||||
</AccordionContent>
|
|
||||||
</AccordionItem>
|
|
||||||
<AccordionItem value="item-3">
|
|
||||||
<AccordionTrigger>Is it animated?</AccordionTrigger>
|
|
||||||
<AccordionContent>
|
|
||||||
Yes. It's animated by default, but you can disable it if you
|
|
||||||
prefer.
|
|
||||||
</AccordionContent>
|
|
||||||
</AccordionItem>
|
|
||||||
</Accordion>
|
|
||||||
<Accordion type="single" collapsible className="w-full">
|
|
||||||
<AccordionItem value="item-1">
|
|
||||||
<AccordionTrigger>
|
|
||||||
What are the key considerations when implementing a comprehensive
|
|
||||||
enterprise-level authentication system?
|
|
||||||
</AccordionTrigger>
|
|
||||||
<AccordionContent>
|
|
||||||
Implementing a robust enterprise authentication system requires
|
|
||||||
careful consideration of multiple factors. This includes secure
|
|
||||||
password hashing and storage, multi-factor authentication (MFA)
|
|
||||||
implementation, session management, OAuth2 and SSO integration,
|
|
||||||
regular security audits, rate limiting to prevent brute force
|
|
||||||
attacks, and maintaining detailed audit logs. Additionally,
|
|
||||||
you'll need to consider scalability, performance impact, and
|
|
||||||
compliance with relevant data protection regulations such as GDPR or
|
|
||||||
HIPAA.
|
|
||||||
</AccordionContent>
|
|
||||||
</AccordionItem>
|
|
||||||
<AccordionItem value="item-2">
|
|
||||||
<AccordionTrigger>
|
|
||||||
How does modern distributed system architecture handle eventual
|
|
||||||
consistency and data synchronization across multiple regions?
|
|
||||||
</AccordionTrigger>
|
|
||||||
<AccordionContent>
|
|
||||||
Modern distributed systems employ various strategies to maintain
|
|
||||||
data consistency across regions. This often involves using
|
|
||||||
techniques like CRDT (Conflict-Free Replicated Data Types), vector
|
|
||||||
clocks, and gossip protocols. Systems might implement event sourcing
|
|
||||||
patterns, utilize message queues for asynchronous updates, and
|
|
||||||
employ sophisticated conflict resolution strategies. Popular
|
|
||||||
solutions like Amazon's DynamoDB and Google's Spanner
|
|
||||||
demonstrate different approaches to solving these challenges,
|
|
||||||
balancing between consistency, availability, and partition tolerance
|
|
||||||
as described in the CAP theorem.
|
|
||||||
</AccordionContent>
|
|
||||||
</AccordionItem>
|
|
||||||
</Accordion>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import {
|
|
||||||
ReactNode,
|
|
||||||
createContext,
|
|
||||||
useContext,
|
|
||||||
useEffect,
|
|
||||||
useState,
|
|
||||||
} from "react"
|
|
||||||
|
|
||||||
const COOKIE_NAME = "active_theme"
|
|
||||||
const DEFAULT_THEME = "default"
|
|
||||||
|
|
||||||
function setThemeCookie(theme: string) {
|
|
||||||
if (typeof window === "undefined") return
|
|
||||||
|
|
||||||
document.cookie = `${COOKIE_NAME}=${theme}; path=/; max-age=31536000; SameSite=Lax; ${window.location.protocol === "https:" ? "Secure;" : ""}`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ThemeContextType = {
|
|
||||||
activeTheme: string
|
|
||||||
setActiveTheme: (theme: string) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const ThemeContext = createContext<ThemeContextType | undefined>(undefined)
|
|
||||||
|
|
||||||
export function ActiveThemeProvider({
|
|
||||||
children,
|
|
||||||
initialTheme,
|
|
||||||
}: {
|
|
||||||
children: ReactNode
|
|
||||||
initialTheme?: string
|
|
||||||
}) {
|
|
||||||
const [activeTheme, setActiveTheme] = useState<string>(
|
|
||||||
() => initialTheme || DEFAULT_THEME
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setThemeCookie(activeTheme)
|
|
||||||
|
|
||||||
Array.from(document.body.classList)
|
|
||||||
.filter((className) => className.startsWith("theme-"))
|
|
||||||
.forEach((className) => {
|
|
||||||
document.body.classList.remove(className)
|
|
||||||
})
|
|
||||||
document.body.classList.add(`theme-${activeTheme}`)
|
|
||||||
if (activeTheme.endsWith("-scaled")) {
|
|
||||||
document.body.classList.add("theme-scaled")
|
|
||||||
}
|
|
||||||
}, [activeTheme])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ThemeContext.Provider value={{ activeTheme, setActiveTheme }}>
|
|
||||||
{children}
|
|
||||||
</ThemeContext.Provider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useThemeConfig() {
|
|
||||||
const context = useContext(ThemeContext)
|
|
||||||
if (context === undefined) {
|
|
||||||
throw new Error("useThemeConfig must be used within an ActiveThemeProvider")
|
|
||||||
}
|
|
||||||
return context
|
|
||||||
}
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
import {
|
|
||||||
AlertCircleIcon,
|
|
||||||
BookmarkCheckIcon,
|
|
||||||
CheckCircle2Icon,
|
|
||||||
GiftIcon,
|
|
||||||
PopcornIcon,
|
|
||||||
ShieldAlertIcon,
|
|
||||||
} from "lucide-react"
|
|
||||||
|
|
||||||
import {
|
|
||||||
Alert,
|
|
||||||
AlertDescription,
|
|
||||||
AlertTitle,
|
|
||||||
} from "@/registry/new-york-v4/ui/alert"
|
|
||||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
|
||||||
|
|
||||||
export function AlertDemo() {
|
|
||||||
return (
|
|
||||||
<div className="grid max-w-xl items-start gap-4">
|
|
||||||
<Alert>
|
|
||||||
<CheckCircle2Icon />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
This is an alert with icon, title and description.
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
<Alert>
|
|
||||||
<BookmarkCheckIcon>Heads up!</BookmarkCheckIcon>
|
|
||||||
<AlertDescription>
|
|
||||||
This one has an icon and a description only. No title.
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
<Alert>
|
|
||||||
<AlertDescription>
|
|
||||||
This one has a description only. No title. No icon.
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
<Alert>
|
|
||||||
<PopcornIcon />
|
|
||||||
<AlertTitle>Let's try one with icon and title.</AlertTitle>
|
|
||||||
</Alert>
|
|
||||||
<Alert>
|
|
||||||
<ShieldAlertIcon />
|
|
||||||
<AlertTitle>
|
|
||||||
This is a very long alert title that demonstrates how the component
|
|
||||||
handles extended text content and potentially wraps across multiple
|
|
||||||
lines
|
|
||||||
</AlertTitle>
|
|
||||||
</Alert>
|
|
||||||
<Alert>
|
|
||||||
<GiftIcon />
|
|
||||||
<AlertDescription>
|
|
||||||
This is a very long alert description that demonstrates how the
|
|
||||||
component handles extended text content and potentially wraps across
|
|
||||||
multiple lines
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
<Alert>
|
|
||||||
<AlertCircleIcon />
|
|
||||||
<AlertTitle>
|
|
||||||
This is an extremely long alert title that spans multiple lines to
|
|
||||||
demonstrate how the component handles very lengthy headings while
|
|
||||||
maintaining readability and proper text wrapping behavior
|
|
||||||
</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
This is an equally long description that contains detailed information
|
|
||||||
about the alert. It shows how the component can accommodate extensive
|
|
||||||
content while preserving proper spacing, alignment, and readability
|
|
||||||
across different screen sizes and viewport widths. This helps ensure
|
|
||||||
the user experience remains consistent regardless of the content
|
|
||||||
length.
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
<Alert variant="destructive">
|
|
||||||
<AlertCircleIcon />
|
|
||||||
<AlertTitle>Something went wrong!</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
Your session has expired. Please log in again.
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
<Alert variant="destructive">
|
|
||||||
<AlertCircleIcon />
|
|
||||||
<AlertTitle>Unable to process your payment.</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
<p>Please verify your billing information and try again.</p>
|
|
||||||
<ul className="list-inside list-disc text-sm">
|
|
||||||
<li>Check your card details</li>
|
|
||||||
<li>Ensure sufficient funds</li>
|
|
||||||
<li>Verify billing address</li>
|
|
||||||
</ul>
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
<Alert>
|
|
||||||
<CheckCircle2Icon />
|
|
||||||
<AlertTitle className="max-w-[calc(100%-4rem)] overflow-ellipsis">
|
|
||||||
The selected emails have been marked as spam.
|
|
||||||
</AlertTitle>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
className="absolute top-2.5 right-3 h-6 shadow-none"
|
|
||||||
>
|
|
||||||
Undo
|
|
||||||
</Button>
|
|
||||||
</Alert>
|
|
||||||
<Alert className="border-amber-50 bg-amber-50 text-amber-900 dark:border-amber-950 dark:bg-amber-950 dark:text-amber-100">
|
|
||||||
<CheckCircle2Icon />
|
|
||||||
<AlertTitle>Plot Twist: This Alert is Actually Amber!</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
This one has custom colors for light and dark mode.
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import {
|
|
||||||
AlertDialog,
|
|
||||||
AlertDialogAction,
|
|
||||||
AlertDialogCancel,
|
|
||||||
AlertDialogContent,
|
|
||||||
AlertDialogDescription,
|
|
||||||
AlertDialogFooter,
|
|
||||||
AlertDialogHeader,
|
|
||||||
AlertDialogTitle,
|
|
||||||
AlertDialogTrigger,
|
|
||||||
} from "@/registry/new-york-v4/ui/alert-dialog"
|
|
||||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
|
||||||
|
|
||||||
export function AlertDialogDemo() {
|
|
||||||
return (
|
|
||||||
<AlertDialog>
|
|
||||||
<AlertDialogTrigger asChild>
|
|
||||||
<Button variant="outline">Show Dialog</Button>
|
|
||||||
</AlertDialogTrigger>
|
|
||||||
<AlertDialogContent>
|
|
||||||
<AlertDialogHeader>
|
|
||||||
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
|
|
||||||
<AlertDialogDescription>
|
|
||||||
This action cannot be undone. This will permanently delete your
|
|
||||||
account and remove your data from our servers.
|
|
||||||
</AlertDialogDescription>
|
|
||||||
</AlertDialogHeader>
|
|
||||||
<AlertDialogFooter>
|
|
||||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
||||||
<AlertDialogAction>Continue</AlertDialogAction>
|
|
||||||
</AlertDialogFooter>
|
|
||||||
</AlertDialogContent>
|
|
||||||
</AlertDialog>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { Analytics as VercelAnalytics } from "@vercel/analytics/react"
|
|
||||||
|
|
||||||
export function Analytics() {
|
|
||||||
return <VercelAnalytics />
|
|
||||||
}
|
|
||||||
@@ -1,248 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import { Index } from "@/__registry__"
|
|
||||||
import {
|
|
||||||
AudioWaveform,
|
|
||||||
BookOpen,
|
|
||||||
Bot,
|
|
||||||
ChevronRightIcon,
|
|
||||||
Command,
|
|
||||||
GalleryVerticalEnd,
|
|
||||||
Search,
|
|
||||||
Settings2,
|
|
||||||
SquareTerminal,
|
|
||||||
} from "lucide-react"
|
|
||||||
|
|
||||||
import { NavUser } from "@/registry/new-york-v4/blocks/sidebar-07/components/nav-user"
|
|
||||||
import { TeamSwitcher } from "@/registry/new-york-v4/blocks/sidebar-07/components/team-switcher"
|
|
||||||
import {
|
|
||||||
Collapsible,
|
|
||||||
CollapsibleContent,
|
|
||||||
CollapsibleTrigger,
|
|
||||||
} from "@/registry/new-york-v4/ui/collapsible"
|
|
||||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
|
||||||
import {
|
|
||||||
Sidebar,
|
|
||||||
SidebarContent,
|
|
||||||
SidebarFooter,
|
|
||||||
SidebarGroup,
|
|
||||||
SidebarGroupContent,
|
|
||||||
SidebarGroupLabel,
|
|
||||||
SidebarHeader,
|
|
||||||
SidebarInput,
|
|
||||||
SidebarMenu,
|
|
||||||
SidebarMenuButton,
|
|
||||||
SidebarMenuItem,
|
|
||||||
SidebarMenuSub,
|
|
||||||
SidebarMenuSubButton,
|
|
||||||
SidebarMenuSubItem,
|
|
||||||
SidebarRail,
|
|
||||||
} from "@/registry/new-york-v4/ui/sidebar"
|
|
||||||
|
|
||||||
// This is sample data.
|
|
||||||
const data = {
|
|
||||||
user: {
|
|
||||||
name: "shadcn",
|
|
||||||
email: "m@example.com",
|
|
||||||
avatar: "/avatars/shadcn.jpg",
|
|
||||||
},
|
|
||||||
teams: [
|
|
||||||
{
|
|
||||||
name: "Acme Inc",
|
|
||||||
logo: GalleryVerticalEnd,
|
|
||||||
plan: "Enterprise",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Acme Corp.",
|
|
||||||
logo: AudioWaveform,
|
|
||||||
plan: "Startup",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Evil Corp.",
|
|
||||||
logo: Command,
|
|
||||||
plan: "Free",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
navMain: [
|
|
||||||
{
|
|
||||||
title: "Playground",
|
|
||||||
url: "#",
|
|
||||||
icon: SquareTerminal,
|
|
||||||
isActive: true,
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
title: "History",
|
|
||||||
url: "#",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Starred",
|
|
||||||
url: "#",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Settings",
|
|
||||||
url: "#",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Models",
|
|
||||||
url: "#",
|
|
||||||
icon: Bot,
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
title: "Genesis",
|
|
||||||
url: "#",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Explorer",
|
|
||||||
url: "#",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Quantum",
|
|
||||||
url: "#",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Documentation",
|
|
||||||
url: "#",
|
|
||||||
icon: BookOpen,
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
title: "Introduction",
|
|
||||||
url: "#",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Get Started",
|
|
||||||
url: "#",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Tutorials",
|
|
||||||
url: "#",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Changelog",
|
|
||||||
url: "#",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Settings",
|
|
||||||
url: "#",
|
|
||||||
icon: Settings2,
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
title: "General",
|
|
||||||
url: "#",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Team",
|
|
||||||
url: "#",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Billing",
|
|
||||||
url: "#",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Limits",
|
|
||||||
url: "#",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
components: Object.values(Index)
|
|
||||||
.filter((item) => item.type === "registry:ui")
|
|
||||||
.concat([
|
|
||||||
{
|
|
||||||
name: "combobox",
|
|
||||||
},
|
|
||||||
])
|
|
||||||
.sort((a, b) => a.name.localeCompare(b.name)),
|
|
||||||
}
|
|
||||||
|
|
||||||
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
|
||||||
return (
|
|
||||||
<Sidebar collapsible="icon" {...props}>
|
|
||||||
<SidebarHeader>
|
|
||||||
<TeamSwitcher teams={data.teams} />
|
|
||||||
<SidebarGroup className="py-0 group-data-[collapsible=icon]:hidden">
|
|
||||||
<SidebarGroupContent>
|
|
||||||
<form className="relative">
|
|
||||||
<Label htmlFor="search" className="sr-only">
|
|
||||||
Search
|
|
||||||
</Label>
|
|
||||||
<SidebarInput
|
|
||||||
id="search"
|
|
||||||
placeholder="Search the docs..."
|
|
||||||
className="pl-8"
|
|
||||||
/>
|
|
||||||
<Search className="pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 opacity-50 select-none" />
|
|
||||||
</form>
|
|
||||||
</SidebarGroupContent>
|
|
||||||
</SidebarGroup>
|
|
||||||
</SidebarHeader>
|
|
||||||
<SidebarContent>
|
|
||||||
<SidebarGroup>
|
|
||||||
<SidebarGroupLabel>Platform</SidebarGroupLabel>
|
|
||||||
<SidebarMenu>
|
|
||||||
{data.navMain.map((item) => (
|
|
||||||
<Collapsible
|
|
||||||
key={item.title}
|
|
||||||
asChild
|
|
||||||
defaultOpen={item.isActive}
|
|
||||||
className="group/collapsible"
|
|
||||||
>
|
|
||||||
<SidebarMenuItem>
|
|
||||||
<CollapsibleTrigger asChild>
|
|
||||||
<SidebarMenuButton tooltip={item.title}>
|
|
||||||
{item.icon && <item.icon />}
|
|
||||||
<span>{item.title}</span>
|
|
||||||
<ChevronRightIcon className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
|
|
||||||
</SidebarMenuButton>
|
|
||||||
</CollapsibleTrigger>
|
|
||||||
<CollapsibleContent>
|
|
||||||
<SidebarMenuSub>
|
|
||||||
{item.items?.map((subItem) => (
|
|
||||||
<SidebarMenuSubItem key={subItem.title}>
|
|
||||||
<SidebarMenuSubButton asChild>
|
|
||||||
<a href={subItem.url}>
|
|
||||||
<span>{subItem.title}</span>
|
|
||||||
</a>
|
|
||||||
</SidebarMenuSubButton>
|
|
||||||
</SidebarMenuSubItem>
|
|
||||||
))}
|
|
||||||
</SidebarMenuSub>
|
|
||||||
</CollapsibleContent>
|
|
||||||
</SidebarMenuItem>
|
|
||||||
</Collapsible>
|
|
||||||
))}
|
|
||||||
</SidebarMenu>
|
|
||||||
</SidebarGroup>
|
|
||||||
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
|
|
||||||
<SidebarGroupLabel>Components</SidebarGroupLabel>
|
|
||||||
<SidebarMenu>
|
|
||||||
{data.components.map((item) => (
|
|
||||||
<SidebarMenuItem key={item.name}>
|
|
||||||
<SidebarMenuButton asChild>
|
|
||||||
<a href={`/#${item.name}`}>
|
|
||||||
<span>{getComponentName(item.name)}</span>
|
|
||||||
</a>
|
|
||||||
</SidebarMenuButton>
|
|
||||||
</SidebarMenuItem>
|
|
||||||
))}
|
|
||||||
</SidebarMenu>
|
|
||||||
</SidebarGroup>
|
|
||||||
</SidebarContent>
|
|
||||||
<SidebarFooter>
|
|
||||||
<NavUser user={data.user} />
|
|
||||||
</SidebarFooter>
|
|
||||||
<SidebarRail />
|
|
||||||
</Sidebar>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getComponentName(name: string) {
|
|
||||||
// convert kebab-case to title case
|
|
||||||
return name.replace(/-/g, " ").replace(/\b\w/g, (char) => char.toUpperCase())
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import Image from "next/image"
|
|
||||||
|
|
||||||
import { AspectRatio } from "@/registry/new-york-v4/ui/aspect-ratio"
|
|
||||||
|
|
||||||
export function AspectRatioDemo() {
|
|
||||||
return (
|
|
||||||
<div className="grid w-full max-w-sm items-start gap-4">
|
|
||||||
<AspectRatio ratio={16 / 9} className="bg-muted rounded-lg">
|
|
||||||
<Image
|
|
||||||
src="https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80"
|
|
||||||
alt="Photo by Drew Beamer"
|
|
||||||
fill
|
|
||||||
className="h-full w-full rounded-lg object-cover dark:brightness-[0.2] dark:grayscale"
|
|
||||||
/>
|
|
||||||
</AspectRatio>
|
|
||||||
<AspectRatio ratio={1 / 1} className="bg-muted rounded-lg">
|
|
||||||
<Image
|
|
||||||
src="https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80"
|
|
||||||
alt="Photo by Drew Beamer"
|
|
||||||
fill
|
|
||||||
className="h-full w-full rounded-lg object-cover dark:brightness-[0.2] dark:grayscale"
|
|
||||||
/>
|
|
||||||
</AspectRatio>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
import {
|
|
||||||
Avatar,
|
|
||||||
AvatarFallback,
|
|
||||||
AvatarImage,
|
|
||||||
} from "@/registry/new-york-v4/ui/avatar"
|
|
||||||
|
|
||||||
export function AvatarDemo() {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-row flex-wrap items-center gap-4">
|
|
||||||
<Avatar>
|
|
||||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
|
||||||
<AvatarFallback>CN</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
<Avatar>
|
|
||||||
<AvatarFallback>CN</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
<Avatar className="size-12">
|
|
||||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
|
||||||
<AvatarFallback>CN</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
<Avatar className="rounded-lg">
|
|
||||||
<AvatarImage
|
|
||||||
src="https://github.com/evilrabbit.png"
|
|
||||||
alt="@evilrabbit"
|
|
||||||
/>
|
|
||||||
<AvatarFallback>ER</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
<div className="*:data-[slot=avatar]:ring-background flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale">
|
|
||||||
<Avatar>
|
|
||||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
|
||||||
<AvatarFallback>CN</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
<Avatar>
|
|
||||||
<AvatarImage src="https://github.com/leerob.png" alt="@leerob" />
|
|
||||||
<AvatarFallback>LR</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
<Avatar>
|
|
||||||
<AvatarImage
|
|
||||||
src="https://github.com/evilrabbit.png"
|
|
||||||
alt="@evilrabbit"
|
|
||||||
/>
|
|
||||||
<AvatarFallback>ER</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
</div>
|
|
||||||
<div className="*:data-[slot=avatar]:ring-background flex -space-x-2 *:data-[slot=avatar]:size-12 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale">
|
|
||||||
<Avatar>
|
|
||||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
|
||||||
<AvatarFallback>CN</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
<Avatar>
|
|
||||||
<AvatarImage src="https://github.com/leerob.png" alt="@leerob" />
|
|
||||||
<AvatarFallback>LR</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
<Avatar>
|
|
||||||
<AvatarImage
|
|
||||||
src="https://github.com/evilrabbit.png"
|
|
||||||
alt="@evilrabbit"
|
|
||||||
/>
|
|
||||||
<AvatarFallback>ER</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
</div>
|
|
||||||
<div className="*:data-[slot=avatar]:ring-background flex -space-x-2 hover:space-x-1 *:data-[slot=avatar]:size-12 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale *:data-[slot=avatar]:transition-all *:data-[slot=avatar]:duration-300 *:data-[slot=avatar]:ease-in-out">
|
|
||||||
<Avatar>
|
|
||||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
|
||||||
<AvatarFallback>CN</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
<Avatar>
|
|
||||||
<AvatarImage src="https://github.com/leerob.png" alt="@leerob" />
|
|
||||||
<AvatarFallback>LR</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
<Avatar>
|
|
||||||
<AvatarImage
|
|
||||||
src="https://github.com/evilrabbit.png"
|
|
||||||
alt="@evilrabbit"
|
|
||||||
/>
|
|
||||||
<AvatarFallback>ER</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
import { AlertCircleIcon, ArrowRightIcon, CheckIcon } from "lucide-react"
|
|
||||||
|
|
||||||
import { Badge } from "@/registry/new-york-v4/ui/badge"
|
|
||||||
|
|
||||||
export function BadgeDemo() {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col items-center gap-2">
|
|
||||||
<div className="flex w-full flex-wrap gap-2">
|
|
||||||
<Badge>Badge</Badge>
|
|
||||||
<Badge variant="secondary">Secondary</Badge>
|
|
||||||
<Badge variant="destructive">Destructive</Badge>
|
|
||||||
<Badge variant="outline">Outline</Badge>
|
|
||||||
<Badge variant="outline">
|
|
||||||
<CheckIcon />
|
|
||||||
Badge
|
|
||||||
</Badge>
|
|
||||||
<Badge variant="destructive">
|
|
||||||
<AlertCircleIcon />
|
|
||||||
Alert
|
|
||||||
</Badge>
|
|
||||||
<Badge className="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums">
|
|
||||||
8
|
|
||||||
</Badge>
|
|
||||||
<Badge
|
|
||||||
className="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums"
|
|
||||||
variant="destructive"
|
|
||||||
>
|
|
||||||
99
|
|
||||||
</Badge>
|
|
||||||
<Badge
|
|
||||||
className="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums"
|
|
||||||
variant="outline"
|
|
||||||
>
|
|
||||||
20+
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
<div className="flex w-full flex-wrap gap-2">
|
|
||||||
<Badge asChild>
|
|
||||||
<a href="#">
|
|
||||||
Link <ArrowRightIcon />
|
|
||||||
</a>
|
|
||||||
</Badge>
|
|
||||||
<Badge asChild variant="secondary">
|
|
||||||
<a href="#">
|
|
||||||
Link <ArrowRightIcon />
|
|
||||||
</a>
|
|
||||||
</Badge>
|
|
||||||
<Badge asChild variant="destructive">
|
|
||||||
<a href="#">
|
|
||||||
Link <ArrowRightIcon />
|
|
||||||
</a>
|
|
||||||
</Badge>
|
|
||||||
<Badge asChild variant="outline">
|
|
||||||
<a href="#">
|
|
||||||
Link <ArrowRightIcon />
|
|
||||||
</a>
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
import {
|
|
||||||
Breadcrumb,
|
|
||||||
BreadcrumbEllipsis,
|
|
||||||
BreadcrumbItem,
|
|
||||||
BreadcrumbLink,
|
|
||||||
BreadcrumbList,
|
|
||||||
BreadcrumbPage,
|
|
||||||
BreadcrumbSeparator,
|
|
||||||
} from "@/registry/new-york-v4/ui/breadcrumb"
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "@/registry/new-york-v4/ui/dropdown-menu"
|
|
||||||
|
|
||||||
export function BreadcrumbDemo() {
|
|
||||||
return (
|
|
||||||
<Breadcrumb>
|
|
||||||
<BreadcrumbList>
|
|
||||||
<BreadcrumbItem>
|
|
||||||
<BreadcrumbLink href="/">Home</BreadcrumbLink>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
<BreadcrumbSeparator />
|
|
||||||
<BreadcrumbItem>
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger className="flex items-center gap-1">
|
|
||||||
<BreadcrumbEllipsis className="h-4 w-4" />
|
|
||||||
<span className="sr-only">Toggle menu</span>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align="start">
|
|
||||||
<DropdownMenuItem>Documentation</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem>Themes</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem>GitHub</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
<BreadcrumbSeparator />
|
|
||||||
<BreadcrumbItem>
|
|
||||||
<BreadcrumbLink href="/docs/components">Components</BreadcrumbLink>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
<BreadcrumbSeparator />
|
|
||||||
<BreadcrumbItem>
|
|
||||||
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
</BreadcrumbList>
|
|
||||||
</Breadcrumb>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
import { ArrowRightIcon, Loader2Icon, SendIcon } from "lucide-react"
|
|
||||||
|
|
||||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
|
||||||
|
|
||||||
export function ButtonDemo() {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col gap-6">
|
|
||||||
<div className="flex flex-wrap items-center gap-2 md:flex-row">
|
|
||||||
<Button>Button</Button>
|
|
||||||
<Button variant="outline">Outline</Button>
|
|
||||||
<Button variant="ghost">Ghost</Button>
|
|
||||||
<Button variant="destructive">Destructive</Button>
|
|
||||||
<Button variant="secondary">Secondary</Button>
|
|
||||||
<Button variant="link">Link</Button>
|
|
||||||
<Button variant="outline">
|
|
||||||
<SendIcon /> Send
|
|
||||||
</Button>
|
|
||||||
<Button variant="outline">
|
|
||||||
Learn More <ArrowRightIcon />
|
|
||||||
</Button>
|
|
||||||
<Button disabled variant="outline">
|
|
||||||
<Loader2Icon className="animate-spin" />
|
|
||||||
Please wait
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-wrap items-center gap-2 md:flex-row">
|
|
||||||
<Button size="sm">Small</Button>
|
|
||||||
<Button variant="outline" size="sm">
|
|
||||||
Outline
|
|
||||||
</Button>
|
|
||||||
<Button variant="ghost" size="sm">
|
|
||||||
Ghost
|
|
||||||
</Button>
|
|
||||||
<Button variant="destructive" size="sm">
|
|
||||||
Destructive
|
|
||||||
</Button>
|
|
||||||
<Button variant="secondary" size="sm">
|
|
||||||
Secondary
|
|
||||||
</Button>
|
|
||||||
<Button variant="link" size="sm">
|
|
||||||
Link
|
|
||||||
</Button>
|
|
||||||
<Button variant="outline" size="sm">
|
|
||||||
<SendIcon /> Send
|
|
||||||
</Button>
|
|
||||||
<Button variant="outline" size="sm">
|
|
||||||
Learn More <ArrowRightIcon />
|
|
||||||
</Button>
|
|
||||||
<Button disabled size="sm" variant="outline">
|
|
||||||
<Loader2Icon className="animate-spin" />
|
|
||||||
Please wait
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-wrap items-center gap-2 md:flex-row">
|
|
||||||
<Button size="lg">Large</Button>
|
|
||||||
<Button variant="outline" size="lg">
|
|
||||||
Outline
|
|
||||||
</Button>
|
|
||||||
<Button variant="ghost" size="lg">
|
|
||||||
Ghost
|
|
||||||
</Button>
|
|
||||||
<Button variant="destructive" size="lg">
|
|
||||||
Destructive
|
|
||||||
</Button>
|
|
||||||
<Button variant="secondary" size="lg">
|
|
||||||
Secondary
|
|
||||||
</Button>
|
|
||||||
<Button variant="link" size="lg">
|
|
||||||
Link
|
|
||||||
</Button>
|
|
||||||
<Button variant="outline" size="lg">
|
|
||||||
<SendIcon /> Send
|
|
||||||
</Button>
|
|
||||||
<Button variant="outline" size="lg">
|
|
||||||
Learn More <ArrowRightIcon />
|
|
||||||
</Button>
|
|
||||||
<Button disabled size="lg" variant="outline">
|
|
||||||
<Loader2Icon className="animate-spin" />
|
|
||||||
Please wait
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import { addDays } from "date-fns"
|
|
||||||
import { type DateRange } from "react-day-picker"
|
|
||||||
|
|
||||||
import { Calendar } from "@/registry/new-york-v4/ui/calendar"
|
|
||||||
|
|
||||||
export function CalendarDemo() {
|
|
||||||
const [date, setDate] = React.useState<Date | undefined>(new Date())
|
|
||||||
const [dateRange, setDateRange] = React.useState<DateRange | undefined>({
|
|
||||||
from: new Date(new Date().getFullYear(), 0, 12),
|
|
||||||
to: addDays(new Date(new Date().getFullYear(), 0, 12), 30),
|
|
||||||
})
|
|
||||||
const [range, setRange] = React.useState<DateRange | undefined>({
|
|
||||||
from: new Date(new Date().getFullYear(), 0, 12),
|
|
||||||
to: addDays(new Date(new Date().getFullYear(), 0, 12), 50),
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col flex-wrap items-start gap-2 @md:flex-row">
|
|
||||||
<Calendar
|
|
||||||
mode="single"
|
|
||||||
selected={date}
|
|
||||||
onSelect={setDate}
|
|
||||||
className="rounded-md border shadow-sm"
|
|
||||||
/>
|
|
||||||
<Calendar
|
|
||||||
mode="range"
|
|
||||||
defaultMonth={dateRange?.from}
|
|
||||||
selected={dateRange}
|
|
||||||
onSelect={setDateRange}
|
|
||||||
numberOfMonths={2}
|
|
||||||
disabled={(date) => date > new Date() || date < new Date("1900-01-01")}
|
|
||||||
className="rounded-md border shadow-sm"
|
|
||||||
/>
|
|
||||||
<Calendar
|
|
||||||
mode="range"
|
|
||||||
defaultMonth={range?.from}
|
|
||||||
selected={range}
|
|
||||||
onSelect={setRange}
|
|
||||||
numberOfMonths={3}
|
|
||||||
className="hidden rounded-md border shadow-sm @4xl:flex [&>div]:gap-5"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,191 +0,0 @@
|
|||||||
import Image from "next/image"
|
|
||||||
import { BathIcon, BedIcon, LandPlotIcon } from "lucide-react"
|
|
||||||
|
|
||||||
import {
|
|
||||||
Avatar,
|
|
||||||
AvatarFallback,
|
|
||||||
AvatarImage,
|
|
||||||
} from "@/registry/new-york-v4/ui/avatar"
|
|
||||||
import { Badge } from "@/registry/new-york-v4/ui/badge"
|
|
||||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardFooter,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from "@/registry/new-york-v4/ui/card"
|
|
||||||
import { Input } from "@/registry/new-york-v4/ui/input"
|
|
||||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
|
||||||
|
|
||||||
export function CardDemo() {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col items-start gap-4">
|
|
||||||
<Card className="w-full max-w-sm">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Login to your account</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Enter your email below to login to your account
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<form>
|
|
||||||
<div className="flex flex-col gap-6">
|
|
||||||
<div className="grid gap-2">
|
|
||||||
<Label htmlFor="email">Email</Label>
|
|
||||||
<Input
|
|
||||||
id="email"
|
|
||||||
type="email"
|
|
||||||
placeholder="m@example.com"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="grid gap-2">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Label htmlFor="password">Password</Label>
|
|
||||||
<a
|
|
||||||
href="#"
|
|
||||||
className="ml-auto inline-block text-sm underline-offset-4 hover:underline"
|
|
||||||
>
|
|
||||||
Forgot your password?
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<Input id="password" type="password" required />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</CardContent>
|
|
||||||
<CardFooter className="flex-col gap-2">
|
|
||||||
<Button type="submit" className="w-full">
|
|
||||||
Login
|
|
||||||
</Button>
|
|
||||||
<Button variant="outline" className="w-full">
|
|
||||||
Login with Google
|
|
||||||
</Button>
|
|
||||||
<div className="mt-4 text-center text-sm">
|
|
||||||
Don't have an account?{" "}
|
|
||||||
<a href="#" className="underline underline-offset-4">
|
|
||||||
Sign up
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Meeting Notes</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Transcript from the meeting with the client.
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="text-sm">
|
|
||||||
<p>
|
|
||||||
Client requested dashboard redesign with focus on mobile
|
|
||||||
responsiveness.
|
|
||||||
</p>
|
|
||||||
<ol className="mt-4 flex list-decimal flex-col gap-2 pl-6">
|
|
||||||
<li>New analytics widgets for daily/weekly metrics</li>
|
|
||||||
<li>Simplified navigation menu</li>
|
|
||||||
<li>Dark mode support</li>
|
|
||||||
<li>Timeline: 6 weeks</li>
|
|
||||||
<li>Follow-up meeting scheduled for next Tuesday</li>
|
|
||||||
</ol>
|
|
||||||
</CardContent>
|
|
||||||
<CardFooter>
|
|
||||||
<div className="*:data-[slot=avatar]:ring-background flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale">
|
|
||||||
<Avatar>
|
|
||||||
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
|
||||||
<AvatarFallback>CN</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
<Avatar>
|
|
||||||
<AvatarImage src="https://github.com/leerob.png" alt="@leerob" />
|
|
||||||
<AvatarFallback>LR</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
<Avatar>
|
|
||||||
<AvatarImage
|
|
||||||
src="https://github.com/evilrabbit.png"
|
|
||||||
alt="@evilrabbit"
|
|
||||||
/>
|
|
||||||
<AvatarFallback>ER</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
</div>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Is this an image?</CardTitle>
|
|
||||||
<CardDescription>This is a card with an image.</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="px-0">
|
|
||||||
<Image
|
|
||||||
src="https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80"
|
|
||||||
alt="Photo by Drew Beamer"
|
|
||||||
className="aspect-video object-cover"
|
|
||||||
width={500}
|
|
||||||
height={500}
|
|
||||||
/>
|
|
||||||
</CardContent>
|
|
||||||
<CardFooter className="flex items-center gap-2">
|
|
||||||
<Badge variant="outline">
|
|
||||||
<BedIcon /> 4
|
|
||||||
</Badge>
|
|
||||||
<Badge variant="outline">
|
|
||||||
<BathIcon /> 2
|
|
||||||
</Badge>
|
|
||||||
<Badge variant="outline">
|
|
||||||
<LandPlotIcon /> 350m²
|
|
||||||
</Badge>
|
|
||||||
<div className="ml-auto font-medium tabular-nums">$135,000</div>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
<div className="flex w-full flex-wrap items-start gap-8 md:*:data-[slot=card]:basis-1/4">
|
|
||||||
<Card>
|
|
||||||
<CardContent className="text-sm">Content Only</CardContent>
|
|
||||||
</Card>
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Header Only</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
This is a card with a header and a description.
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
</Card>
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Header and Content</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
This is a card with a header and a content.
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="text-sm">Content</CardContent>
|
|
||||||
</Card>
|
|
||||||
<Card>
|
|
||||||
<CardFooter className="text-sm">Footer Only</CardFooter>
|
|
||||||
</Card>
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Header + Footer</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
This is a card with a header and a footer.
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardFooter className="text-sm">Footer</CardFooter>
|
|
||||||
</Card>
|
|
||||||
<Card>
|
|
||||||
<CardContent className="text-sm">Content</CardContent>
|
|
||||||
<CardFooter className="text-sm">Footer</CardFooter>
|
|
||||||
</Card>
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Header + Footer</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
This is a card with a header and a footer.
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="text-sm">Content</CardContent>
|
|
||||||
<CardFooter className="text-sm">Footer</CardFooter>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
import * as React from "react"
|
|
||||||
|
|
||||||
import { Card, CardContent } from "@/registry/new-york-v4/ui/card"
|
|
||||||
import {
|
|
||||||
Carousel,
|
|
||||||
CarouselContent,
|
|
||||||
CarouselItem,
|
|
||||||
CarouselNext,
|
|
||||||
CarouselPrevious,
|
|
||||||
} from "@/registry/new-york-v4/ui/carousel"
|
|
||||||
|
|
||||||
export function CarouselDemo() {
|
|
||||||
return (
|
|
||||||
<div className="hidden w-full flex-col items-center gap-4 @4xl:flex">
|
|
||||||
<Carousel className="max-w-sm *:data-[slot=carousel-next]:hidden *:data-[slot=carousel-previous]:hidden *:data-[slot=carousel-next]:md:inline-flex *:data-[slot=carousel-previous]:md:inline-flex">
|
|
||||||
<CarouselContent>
|
|
||||||
{Array.from({ length: 5 }).map((_, index) => (
|
|
||||||
<CarouselItem key={index}>
|
|
||||||
<div className="p-1">
|
|
||||||
<Card>
|
|
||||||
<CardContent className="flex aspect-square items-center justify-center p-6">
|
|
||||||
<span className="text-4xl font-semibold">{index + 1}</span>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
</CarouselItem>
|
|
||||||
))}
|
|
||||||
</CarouselContent>
|
|
||||||
<CarouselPrevious />
|
|
||||||
<CarouselNext />
|
|
||||||
</Carousel>
|
|
||||||
<Carousel
|
|
||||||
className="max-w-sm *:data-[slot=carousel-next]:hidden *:data-[slot=carousel-previous]:hidden *:data-[slot=carousel-next]:md:inline-flex *:data-[slot=carousel-previous]:md:inline-flex"
|
|
||||||
opts={{
|
|
||||||
align: "start",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CarouselContent>
|
|
||||||
{Array.from({ length: 5 }).map((_, index) => (
|
|
||||||
<CarouselItem key={index} className="md:basis-1/2 lg:basis-1/3">
|
|
||||||
<div className="p-1">
|
|
||||||
<Card>
|
|
||||||
<CardContent className="flex aspect-square items-center justify-center p-6">
|
|
||||||
<span className="text-3xl font-semibold">{index + 1}</span>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
</CarouselItem>
|
|
||||||
))}
|
|
||||||
</CarouselContent>
|
|
||||||
<CarouselPrevious />
|
|
||||||
<CarouselNext />
|
|
||||||
</Carousel>
|
|
||||||
<Carousel className="max-w-sm *:data-[slot=carousel-next]:hidden *:data-[slot=carousel-previous]:hidden *:data-[slot=carousel-next]:md:inline-flex *:data-[slot=carousel-previous]:md:inline-flex">
|
|
||||||
<CarouselContent className="-ml-1">
|
|
||||||
{Array.from({ length: 5 }).map((_, index) => (
|
|
||||||
<CarouselItem key={index} className="pl-1 md:basis-1/2">
|
|
||||||
<div className="p-1">
|
|
||||||
<Card>
|
|
||||||
<CardContent className="flex aspect-square items-center justify-center p-6">
|
|
||||||
<span className="text-2xl font-semibold">{index + 1}</span>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
</CarouselItem>
|
|
||||||
))}
|
|
||||||
</CarouselContent>
|
|
||||||
<CarouselPrevious />
|
|
||||||
<CarouselNext />
|
|
||||||
</Carousel>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { TrendingUp } from "lucide-react"
|
|
||||||
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"
|
|
||||||
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardFooter,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from "@/registry/new-york-v4/ui/card"
|
|
||||||
import {
|
|
||||||
ChartConfig,
|
|
||||||
ChartContainer,
|
|
||||||
ChartTooltip,
|
|
||||||
ChartTooltipContent,
|
|
||||||
} from "@/registry/new-york-v4/ui/chart"
|
|
||||||
|
|
||||||
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: "var(--chart-1)",
|
|
||||||
},
|
|
||||||
} satisfies ChartConfig
|
|
||||||
|
|
||||||
export function ChartAreaDemo() {
|
|
||||||
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 leading-none font-medium">
|
|
||||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
|
||||||
</div>
|
|
||||||
<div className="text-muted-foreground flex items-center gap-2 leading-none">
|
|
||||||
January - June 2024
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { TrendingUp } from "lucide-react"
|
|
||||||
import { Bar, BarChart, CartesianGrid, XAxis } from "recharts"
|
|
||||||
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardFooter,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from "@/registry/new-york-v4/ui/card"
|
|
||||||
import {
|
|
||||||
ChartConfig,
|
|
||||||
ChartContainer,
|
|
||||||
ChartTooltip,
|
|
||||||
ChartTooltipContent,
|
|
||||||
} from "@/registry/new-york-v4/ui/chart"
|
|
||||||
|
|
||||||
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: "var(--chart-1)",
|
|
||||||
},
|
|
||||||
mobile: {
|
|
||||||
label: "Mobile",
|
|
||||||
color: "var(--chart-2)",
|
|
||||||
},
|
|
||||||
} satisfies ChartConfig
|
|
||||||
|
|
||||||
export function ChartBarDemo() {
|
|
||||||
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 leading-none font-medium">
|
|
||||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
|
||||||
</div>
|
|
||||||
<div className="text-muted-foreground leading-none">
|
|
||||||
Showing total visitors for the last 6 months
|
|
||||||
</div>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { TrendingUp } from "lucide-react"
|
|
||||||
import { Bar, BarChart, XAxis, YAxis } from "recharts"
|
|
||||||
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardFooter,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from "@/registry/new-york-v4/ui/card"
|
|
||||||
import {
|
|
||||||
ChartConfig,
|
|
||||||
ChartContainer,
|
|
||||||
ChartTooltip,
|
|
||||||
ChartTooltipContent,
|
|
||||||
} from "@/registry/new-york-v4/ui/chart"
|
|
||||||
|
|
||||||
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: "var(--chart-1)",
|
|
||||||
},
|
|
||||||
safari: {
|
|
||||||
label: "Safari",
|
|
||||||
color: "var(--chart-2)",
|
|
||||||
},
|
|
||||||
firefox: {
|
|
||||||
label: "Firefox",
|
|
||||||
color: "var(--chart-3)",
|
|
||||||
},
|
|
||||||
edge: {
|
|
||||||
label: "Edge",
|
|
||||||
color: "var(--chart-4)",
|
|
||||||
},
|
|
||||||
other: {
|
|
||||||
label: "Other",
|
|
||||||
color: "var(--chart-5)",
|
|
||||||
},
|
|
||||||
} satisfies ChartConfig
|
|
||||||
|
|
||||||
export function ChartBarMixed() {
|
|
||||||
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 leading-none font-medium">
|
|
||||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
|
||||||
</div>
|
|
||||||
<div className="text-muted-foreground leading-none">
|
|
||||||
Showing total visitors for the last 6 months
|
|
||||||
</div>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import { ChartAreaDemo } from "@/components/chart-area-demo"
|
|
||||||
import { ChartBarDemo } from "@/components/chart-bar-demo"
|
|
||||||
import { ChartLineDemo } from "@/components/chart-line-demo"
|
|
||||||
import { ChartBarMixed } from "@/registry/new-york-v4/charts/chart-bar-mixed"
|
|
||||||
|
|
||||||
export function ChartDemo() {
|
|
||||||
return (
|
|
||||||
<div className="grid w-full max-w-screen-2xl gap-4 *:data-[slot=card]:flex-1 @2xl:grid-cols-2 @6xl:grid-cols-3">
|
|
||||||
<ChartAreaDemo />
|
|
||||||
<ChartBarDemo />
|
|
||||||
<ChartBarMixed />
|
|
||||||
<div className="@6xl:hidden">
|
|
||||||
<ChartLineDemo />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { TrendingUp } from "lucide-react"
|
|
||||||
import { CartesianGrid, Line, LineChart, XAxis } from "recharts"
|
|
||||||
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardFooter,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from "@/registry/new-york-v4/ui/card"
|
|
||||||
import {
|
|
||||||
ChartConfig,
|
|
||||||
ChartContainer,
|
|
||||||
ChartTooltip,
|
|
||||||
ChartTooltipContent,
|
|
||||||
} from "@/registry/new-york-v4/ui/chart"
|
|
||||||
|
|
||||||
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: "var(--chart-1)",
|
|
||||||
},
|
|
||||||
mobile: {
|
|
||||||
label: "Mobile",
|
|
||||||
color: "var(--chart-2)",
|
|
||||||
},
|
|
||||||
} satisfies ChartConfig
|
|
||||||
|
|
||||||
export function ChartLineDemo() {
|
|
||||||
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 leading-none font-medium">
|
|
||||||
Trending up by 5.2% this month <TrendingUp className="h-4 w-4" />
|
|
||||||
</div>
|
|
||||||
<div className="text-muted-foreground flex items-center gap-2 leading-none">
|
|
||||||
Showing total visitors for the last 6 months
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { Checkbox } from "@/registry/new-york-v4/ui/checkbox"
|
|
||||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
|
||||||
|
|
||||||
export function CheckboxDemo() {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col gap-6">
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<Checkbox id="terms" />
|
|
||||||
<Label htmlFor="terms">Accept terms and conditions</Label>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-start gap-3">
|
|
||||||
<Checkbox id="terms-2" defaultChecked />
|
|
||||||
<div className="grid gap-2">
|
|
||||||
<Label htmlFor="terms-2">Accept terms and conditions</Label>
|
|
||||||
<p className="text-muted-foreground text-sm">
|
|
||||||
By clicking this checkbox, you agree to the terms and conditions.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-start gap-3">
|
|
||||||
<Checkbox id="toggle" disabled />
|
|
||||||
<Label htmlFor="toggle">Enable notifications</Label>
|
|
||||||
</div>
|
|
||||||
<Label className="hover:bg-accent/50 flex items-start gap-3 rounded-lg border p-3 has-[[aria-checked=true]]:border-blue-600 has-[[aria-checked=true]]:bg-blue-50 dark:has-[[aria-checked=true]]:border-blue-900 dark:has-[[aria-checked=true]]:bg-blue-950">
|
|
||||||
<Checkbox
|
|
||||||
id="toggle-2"
|
|
||||||
defaultChecked
|
|
||||||
className="data-[state=checked]:border-blue-600 data-[state=checked]:bg-blue-600 data-[state=checked]:text-white dark:data-[state=checked]:border-blue-700 dark:data-[state=checked]:bg-blue-700"
|
|
||||||
/>
|
|
||||||
<div className="grid gap-1.5 font-normal">
|
|
||||||
<p className="text-sm leading-none font-medium">
|
|
||||||
Enable notifications
|
|
||||||
</p>
|
|
||||||
<p className="text-muted-foreground text-sm">
|
|
||||||
You can enable or disable notifications at any time.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import { ChevronsUpDown } from "lucide-react"
|
|
||||||
|
|
||||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
|
||||||
import {
|
|
||||||
Collapsible,
|
|
||||||
CollapsibleContent,
|
|
||||||
CollapsibleTrigger,
|
|
||||||
} from "@/registry/new-york-v4/ui/collapsible"
|
|
||||||
|
|
||||||
export function CollapsibleDemo() {
|
|
||||||
const [isOpen, setIsOpen] = React.useState(false)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Collapsible
|
|
||||||
open={isOpen}
|
|
||||||
onOpenChange={setIsOpen}
|
|
||||||
className="flex w-full flex-col gap-2 md:w-[350px]"
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-between gap-4 px-4">
|
|
||||||
<h4 className="line-clamp-1 text-sm font-semibold">
|
|
||||||
@peduarte starred 3 repositories
|
|
||||||
</h4>
|
|
||||||
<CollapsibleTrigger asChild>
|
|
||||||
<Button variant="ghost" size="sm">
|
|
||||||
<ChevronsUpDown className="h-4 w-4" />
|
|
||||||
<span className="sr-only">Toggle</span>
|
|
||||||
</Button>
|
|
||||||
</CollapsibleTrigger>
|
|
||||||
</div>
|
|
||||||
<div className="rounded-md border px-4 py-2 font-mono text-sm shadow-xs">
|
|
||||||
@radix-ui/primitives
|
|
||||||
</div>
|
|
||||||
<CollapsibleContent className="flex flex-col gap-2">
|
|
||||||
<div className="rounded-md border px-4 py-2 font-mono text-sm shadow-xs">
|
|
||||||
@radix-ui/colors
|
|
||||||
</div>
|
|
||||||
<div className="rounded-md border px-4 py-2 font-mono text-sm shadow-xs">
|
|
||||||
@stitches/react
|
|
||||||
</div>
|
|
||||||
</CollapsibleContent>
|
|
||||||
</Collapsible>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,405 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import {
|
|
||||||
CheckIcon,
|
|
||||||
ChevronDownIcon,
|
|
||||||
ChevronsUpDown,
|
|
||||||
PlusCircleIcon,
|
|
||||||
} from "lucide-react"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
import {
|
|
||||||
Avatar,
|
|
||||||
AvatarFallback,
|
|
||||||
AvatarImage,
|
|
||||||
} from "@/registry/new-york-v4/ui/avatar"
|
|
||||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
|
||||||
import {
|
|
||||||
Command,
|
|
||||||
CommandEmpty,
|
|
||||||
CommandGroup,
|
|
||||||
CommandInput,
|
|
||||||
CommandItem,
|
|
||||||
CommandList,
|
|
||||||
CommandSeparator,
|
|
||||||
} from "@/registry/new-york-v4/ui/command"
|
|
||||||
import {
|
|
||||||
Popover,
|
|
||||||
PopoverContent,
|
|
||||||
PopoverTrigger,
|
|
||||||
} from "@/registry/new-york-v4/ui/popover"
|
|
||||||
|
|
||||||
const frameworks = [
|
|
||||||
{
|
|
||||||
value: "next.js",
|
|
||||||
label: "Next.js",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "sveltekit",
|
|
||||||
label: "SvelteKit",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "nuxt.js",
|
|
||||||
label: "Nuxt.js",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "remix",
|
|
||||||
label: "Remix",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: "astro",
|
|
||||||
label: "Astro",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
type Framework = (typeof frameworks)[number]
|
|
||||||
|
|
||||||
const users = [
|
|
||||||
{
|
|
||||||
id: "1",
|
|
||||||
username: "shadcn",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "2",
|
|
||||||
username: "leerob",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "3",
|
|
||||||
username: "evilrabbit",
|
|
||||||
},
|
|
||||||
] as const
|
|
||||||
|
|
||||||
type User = (typeof users)[number]
|
|
||||||
|
|
||||||
const timezones = [
|
|
||||||
{
|
|
||||||
label: "Americas",
|
|
||||||
timezones: [
|
|
||||||
{ value: "America/New_York", label: "(GMT-5) New York" },
|
|
||||||
{ value: "America/Los_Angeles", label: "(GMT-8) Los Angeles" },
|
|
||||||
{ value: "America/Chicago", label: "(GMT-6) Chicago" },
|
|
||||||
{ value: "America/Toronto", label: "(GMT-5) Toronto" },
|
|
||||||
{ value: "America/Vancouver", label: "(GMT-8) Vancouver" },
|
|
||||||
{ value: "America/Sao_Paulo", label: "(GMT-3) São Paulo" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Europe",
|
|
||||||
timezones: [
|
|
||||||
{ value: "Europe/London", label: "(GMT+0) London" },
|
|
||||||
{ value: "Europe/Paris", label: "(GMT+1) Paris" },
|
|
||||||
{ value: "Europe/Berlin", label: "(GMT+1) Berlin" },
|
|
||||||
{ value: "Europe/Rome", label: "(GMT+1) Rome" },
|
|
||||||
{ value: "Europe/Madrid", label: "(GMT+1) Madrid" },
|
|
||||||
{ value: "Europe/Amsterdam", label: "(GMT+1) Amsterdam" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Asia/Pacific",
|
|
||||||
timezones: [
|
|
||||||
{ value: "Asia/Tokyo", label: "(GMT+9) Tokyo" },
|
|
||||||
{ value: "Asia/Shanghai", label: "(GMT+8) Shanghai" },
|
|
||||||
{ value: "Asia/Singapore", label: "(GMT+8) Singapore" },
|
|
||||||
{ value: "Asia/Dubai", label: "(GMT+4) Dubai" },
|
|
||||||
{ value: "Australia/Sydney", label: "(GMT+11) Sydney" },
|
|
||||||
{ value: "Asia/Seoul", label: "(GMT+9) Seoul" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
] as const
|
|
||||||
|
|
||||||
type Timezone = (typeof timezones)[number]
|
|
||||||
|
|
||||||
export function ComboboxDemo() {
|
|
||||||
return (
|
|
||||||
<div className="flex w-full flex-wrap items-start gap-4">
|
|
||||||
<FrameworkCombobox frameworks={[...frameworks]} />
|
|
||||||
<UserCombobox users={[...users]} selectedUserId={users[0].id} />
|
|
||||||
<TimezoneCombobox
|
|
||||||
timezones={[...timezones]}
|
|
||||||
selectedTimezone={timezones[0].timezones[0]}
|
|
||||||
/>
|
|
||||||
<ComboboxWithCheckbox frameworks={[...frameworks]} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function FrameworkCombobox({ frameworks }: { frameworks: Framework[] }) {
|
|
||||||
const [open, setOpen] = React.useState(false)
|
|
||||||
const [value, setValue] = React.useState("")
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Popover open={open} onOpenChange={setOpen}>
|
|
||||||
<PopoverTrigger asChild>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
role="combobox"
|
|
||||||
aria-expanded={open}
|
|
||||||
className="w-full justify-between md:max-w-[200px]"
|
|
||||||
>
|
|
||||||
{value
|
|
||||||
? frameworks.find((framework) => framework.value === value)?.label
|
|
||||||
: "Select framework..."}
|
|
||||||
<ChevronsUpDown className="text-muted-foreground" />
|
|
||||||
</Button>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent className="w-(--radix-popover-trigger-width) p-0">
|
|
||||||
<Command>
|
|
||||||
<CommandInput placeholder="Search framework..." />
|
|
||||||
<CommandList>
|
|
||||||
<CommandEmpty>No framework found.</CommandEmpty>
|
|
||||||
<CommandGroup>
|
|
||||||
{frameworks.map((framework) => (
|
|
||||||
<CommandItem
|
|
||||||
key={framework.value}
|
|
||||||
value={framework.value}
|
|
||||||
onSelect={(currentValue) => {
|
|
||||||
setValue(currentValue === value ? "" : currentValue)
|
|
||||||
setOpen(false)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{framework.label}
|
|
||||||
<CheckIcon
|
|
||||||
className={cn(
|
|
||||||
"ml-auto",
|
|
||||||
value === framework.value ? "opacity-100" : "opacity-0"
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</CommandItem>
|
|
||||||
))}
|
|
||||||
</CommandGroup>
|
|
||||||
</CommandList>
|
|
||||||
</Command>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function UserCombobox({
|
|
||||||
users,
|
|
||||||
selectedUserId,
|
|
||||||
}: {
|
|
||||||
users: User[]
|
|
||||||
selectedUserId: string
|
|
||||||
}) {
|
|
||||||
const [open, setOpen] = React.useState(false)
|
|
||||||
const [value, setValue] = React.useState(selectedUserId)
|
|
||||||
|
|
||||||
const selectedUser = React.useMemo(
|
|
||||||
() => users.find((user) => user.id === value),
|
|
||||||
[value, users]
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Popover open={open} onOpenChange={setOpen}>
|
|
||||||
<PopoverTrigger asChild>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
role="combobox"
|
|
||||||
aria-expanded={open}
|
|
||||||
className="w-full justify-between px-2 md:max-w-[200px]"
|
|
||||||
>
|
|
||||||
{selectedUser ? (
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Avatar className="size-5">
|
|
||||||
<AvatarImage
|
|
||||||
src={`https://github.com/${selectedUser.username}.png`}
|
|
||||||
/>
|
|
||||||
<AvatarFallback>{selectedUser.username[0]}</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
{selectedUser.username}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
"Select user..."
|
|
||||||
)}
|
|
||||||
<ChevronsUpDown className="text-muted-foreground" />
|
|
||||||
</Button>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent className="w-(--radix-popover-trigger-width) p-0">
|
|
||||||
<Command>
|
|
||||||
<CommandInput placeholder="Search user..." />
|
|
||||||
<CommandList>
|
|
||||||
<CommandEmpty>No user found.</CommandEmpty>
|
|
||||||
<CommandGroup>
|
|
||||||
{users.map((user) => (
|
|
||||||
<CommandItem
|
|
||||||
key={user.id}
|
|
||||||
value={user.id}
|
|
||||||
onSelect={(currentValue) => {
|
|
||||||
setValue(currentValue === value ? "" : currentValue)
|
|
||||||
setOpen(false)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Avatar className="size-5">
|
|
||||||
<AvatarImage
|
|
||||||
src={`https://github.com/${user.username}.png`}
|
|
||||||
/>
|
|
||||||
<AvatarFallback>{user.username[0]}</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
{user.username}
|
|
||||||
<CheckIcon
|
|
||||||
className={cn(
|
|
||||||
"ml-auto",
|
|
||||||
value === user.id ? "opacity-100" : "opacity-0"
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</CommandItem>
|
|
||||||
))}
|
|
||||||
</CommandGroup>
|
|
||||||
<CommandSeparator />
|
|
||||||
<CommandGroup>
|
|
||||||
<CommandItem>
|
|
||||||
<PlusCircleIcon />
|
|
||||||
Create user
|
|
||||||
</CommandItem>
|
|
||||||
</CommandGroup>
|
|
||||||
</CommandList>
|
|
||||||
</Command>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function TimezoneCombobox({
|
|
||||||
timezones,
|
|
||||||
selectedTimezone,
|
|
||||||
}: {
|
|
||||||
timezones: Timezone[]
|
|
||||||
selectedTimezone: Timezone["timezones"][number]
|
|
||||||
}) {
|
|
||||||
const [open, setOpen] = React.useState(false)
|
|
||||||
const [value, setValue] = React.useState(selectedTimezone.value)
|
|
||||||
|
|
||||||
const selectedGroup = React.useMemo(
|
|
||||||
() =>
|
|
||||||
timezones.find((group) =>
|
|
||||||
group.timezones.find((tz) => tz.value === value)
|
|
||||||
),
|
|
||||||
[value, timezones]
|
|
||||||
)
|
|
||||||
|
|
||||||
const selectedTimezoneLabel = React.useMemo(
|
|
||||||
() => selectedGroup?.timezones.find((tz) => tz.value === value)?.label,
|
|
||||||
[value, selectedGroup]
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Popover open={open} onOpenChange={setOpen}>
|
|
||||||
<PopoverTrigger asChild>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
className="h-12 w-full justify-between px-2.5 md:max-w-[200px]"
|
|
||||||
>
|
|
||||||
{selectedTimezone ? (
|
|
||||||
<div className="flex flex-col items-start gap-0.5">
|
|
||||||
<span className="text-muted-foreground text-xs font-normal">
|
|
||||||
{selectedGroup?.label}
|
|
||||||
</span>
|
|
||||||
<span>{selectedTimezoneLabel}</span>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
"Select timezone"
|
|
||||||
)}
|
|
||||||
<ChevronDownIcon className="text-muted-foreground" />
|
|
||||||
</Button>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent className="p-0" align="start">
|
|
||||||
<Command>
|
|
||||||
<CommandInput placeholder="Search timezone..." />
|
|
||||||
<CommandList className="scroll-pb-12">
|
|
||||||
<CommandEmpty>No timezone found.</CommandEmpty>
|
|
||||||
{timezones.map((region) => (
|
|
||||||
<CommandGroup key={region.label} heading={region.label}>
|
|
||||||
{region.timezones.map((timezone) => (
|
|
||||||
<CommandItem
|
|
||||||
key={timezone.value}
|
|
||||||
value={timezone.value}
|
|
||||||
onSelect={(currentValue) => {
|
|
||||||
setValue(
|
|
||||||
currentValue as Timezone["timezones"][number]["value"]
|
|
||||||
)
|
|
||||||
setOpen(false)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{timezone.label}
|
|
||||||
<CheckIcon
|
|
||||||
className="ml-auto opacity-0 data-[selected=true]:opacity-100"
|
|
||||||
data-selected={value === timezone.value}
|
|
||||||
/>
|
|
||||||
</CommandItem>
|
|
||||||
))}
|
|
||||||
</CommandGroup>
|
|
||||||
))}
|
|
||||||
<CommandSeparator className="sticky bottom-10" />
|
|
||||||
<CommandGroup className="bg-popover sticky bottom-0">
|
|
||||||
<CommandItem>
|
|
||||||
<PlusCircleIcon />
|
|
||||||
Create timezone
|
|
||||||
</CommandItem>
|
|
||||||
</CommandGroup>
|
|
||||||
</CommandList>
|
|
||||||
</Command>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function ComboboxWithCheckbox({ frameworks }: { frameworks: Framework[] }) {
|
|
||||||
const [open, setOpen] = React.useState(false)
|
|
||||||
const [selectedFrameworks, setSelectedFrameworks] = React.useState<
|
|
||||||
Framework[]
|
|
||||||
>([])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Popover open={open} onOpenChange={setOpen}>
|
|
||||||
<PopoverTrigger asChild>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
role="combobox"
|
|
||||||
aria-expanded={open}
|
|
||||||
className="w-fit min-w-[280px] justify-between"
|
|
||||||
>
|
|
||||||
{selectedFrameworks.length > 0
|
|
||||||
? selectedFrameworks.map((framework) => framework.label).join(", ")
|
|
||||||
: "Select frameworks (multi-select)..."}
|
|
||||||
<ChevronsUpDown className="text-muted-foreground" />
|
|
||||||
</Button>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent className="w-[300px] p-0" align="start">
|
|
||||||
<Command>
|
|
||||||
<CommandInput placeholder="Search framework..." />
|
|
||||||
<CommandList>
|
|
||||||
<CommandEmpty>No framework found.</CommandEmpty>
|
|
||||||
<CommandGroup>
|
|
||||||
{frameworks.map((framework) => (
|
|
||||||
<CommandItem
|
|
||||||
key={framework.value}
|
|
||||||
value={framework.value}
|
|
||||||
onSelect={(currentValue) => {
|
|
||||||
setSelectedFrameworks(
|
|
||||||
selectedFrameworks.some((f) => f.value === currentValue)
|
|
||||||
? selectedFrameworks.filter(
|
|
||||||
(f) => f.value !== currentValue
|
|
||||||
)
|
|
||||||
: [...selectedFrameworks, framework]
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="border-input data-[selected=true]:border-primary data-[selected=true]:bg-primary data-[selected=true]:text-primary-foreground pointer-events-none size-4 shrink-0 rounded-[4px] border transition-all select-none *:[svg]:opacity-0 data-[selected=true]:*:[svg]:opacity-100"
|
|
||||||
data-selected={selectedFrameworks.some(
|
|
||||||
(f) => f.value === framework.value
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<CheckIcon className="size-3.5 text-current" />
|
|
||||||
</div>
|
|
||||||
{framework.label}
|
|
||||||
</CommandItem>
|
|
||||||
))}
|
|
||||||
</CommandGroup>
|
|
||||||
</CommandList>
|
|
||||||
</Command>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import {
|
|
||||||
Calculator,
|
|
||||||
Calendar,
|
|
||||||
CreditCard,
|
|
||||||
Settings,
|
|
||||||
Smile,
|
|
||||||
User,
|
|
||||||
} from "lucide-react"
|
|
||||||
|
|
||||||
import {
|
|
||||||
CommandDialog,
|
|
||||||
CommandEmpty,
|
|
||||||
CommandGroup,
|
|
||||||
CommandInput,
|
|
||||||
CommandItem,
|
|
||||||
CommandList,
|
|
||||||
CommandSeparator,
|
|
||||||
CommandShortcut,
|
|
||||||
} from "@/registry/new-york-v4/ui/command"
|
|
||||||
|
|
||||||
export function CommandDemo() {
|
|
||||||
const [open, setOpen] = React.useState(false)
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
const down = (e: KeyboardEvent) => {
|
|
||||||
if (e.key === "j" && (e.metaKey || e.ctrlKey)) {
|
|
||||||
e.preventDefault()
|
|
||||||
setOpen((open) => !open)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener("keydown", down)
|
|
||||||
return () => document.removeEventListener("keydown", down)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<p className="text-muted-foreground text-sm">
|
|
||||||
Press{" "}
|
|
||||||
<kbd className="bg-muted text-muted-foreground pointer-events-none inline-flex h-5 items-center gap-1 rounded border px-1.5 font-mono text-[10px] font-medium opacity-100 select-none">
|
|
||||||
<span className="text-xs">⌘</span>J
|
|
||||||
</kbd>
|
|
||||||
</p>
|
|
||||||
<CommandDialog open={open} onOpenChange={setOpen}>
|
|
||||||
<CommandInput placeholder="Type a command or search..." />
|
|
||||||
<CommandList>
|
|
||||||
<CommandEmpty>No results found.</CommandEmpty>
|
|
||||||
<CommandGroup heading="Suggestions">
|
|
||||||
<CommandItem>
|
|
||||||
<Calendar />
|
|
||||||
<span>Calendar</span>
|
|
||||||
</CommandItem>
|
|
||||||
<CommandItem>
|
|
||||||
<Smile />
|
|
||||||
<span>Search Emoji</span>
|
|
||||||
</CommandItem>
|
|
||||||
<CommandItem>
|
|
||||||
<Calculator />
|
|
||||||
<span>Calculator</span>
|
|
||||||
</CommandItem>
|
|
||||||
</CommandGroup>
|
|
||||||
<CommandSeparator />
|
|
||||||
<CommandGroup heading="Settings">
|
|
||||||
<CommandItem>
|
|
||||||
<User />
|
|
||||||
<span>Profile</span>
|
|
||||||
<CommandShortcut>⌘P</CommandShortcut>
|
|
||||||
</CommandItem>
|
|
||||||
<CommandItem>
|
|
||||||
<CreditCard />
|
|
||||||
<span>Billing</span>
|
|
||||||
<CommandShortcut>⌘B</CommandShortcut>
|
|
||||||
</CommandItem>
|
|
||||||
<CommandItem>
|
|
||||||
<Settings />
|
|
||||||
<span>Settings</span>
|
|
||||||
<CommandShortcut>⌘S</CommandShortcut>
|
|
||||||
</CommandItem>
|
|
||||||
</CommandGroup>
|
|
||||||
</CommandList>
|
|
||||||
</CommandDialog>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
|
|
||||||
import { cn } from "@/registry/new-york-v4/lib/utils"
|
|
||||||
|
|
||||||
export function ComponentWrapper({
|
|
||||||
className,
|
|
||||||
name,
|
|
||||||
children,
|
|
||||||
...props
|
|
||||||
}: React.ComponentPropsWithoutRef<"div"> & { name: string }) {
|
|
||||||
return (
|
|
||||||
<ComponentErrorBoundary name={name}>
|
|
||||||
<div
|
|
||||||
id={name}
|
|
||||||
data-name={name.toLowerCase()}
|
|
||||||
className={cn(
|
|
||||||
"flex w-full scroll-mt-16 flex-col rounded-lg border",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<div className="border-b px-4 py-3">
|
|
||||||
<div className="text-sm font-medium">{getComponentName(name)}</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-1 items-center gap-2 p-4">{children}</div>
|
|
||||||
</div>
|
|
||||||
</ComponentErrorBoundary>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
class ComponentErrorBoundary extends React.Component<
|
|
||||||
{ children: React.ReactNode; name: string },
|
|
||||||
{ hasError: boolean }
|
|
||||||
> {
|
|
||||||
constructor(props: { children: React.ReactNode; name: string }) {
|
|
||||||
super(props)
|
|
||||||
this.state = { hasError: false }
|
|
||||||
}
|
|
||||||
|
|
||||||
static getDerivedStateFromError() {
|
|
||||||
return { hasError: true }
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
|
||||||
console.error(`Error in component ${this.props.name}:`, error, errorInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
if (this.state.hasError) {
|
|
||||||
return (
|
|
||||||
<div className="p-4 text-red-500">
|
|
||||||
Something went wrong in component: {this.props.name}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.props.children
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getComponentName(name: string) {
|
|
||||||
// convert kebab-case to title case
|
|
||||||
return name.replace(/-/g, " ").replace(/\b\w/g, (char) => char.toUpperCase())
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
import { Code2Icon, PlusIcon, TrashIcon } from "lucide-react"
|
|
||||||
|
|
||||||
import {
|
|
||||||
ContextMenu,
|
|
||||||
ContextMenuCheckboxItem,
|
|
||||||
ContextMenuContent,
|
|
||||||
ContextMenuItem,
|
|
||||||
ContextMenuLabel,
|
|
||||||
ContextMenuRadioGroup,
|
|
||||||
ContextMenuRadioItem,
|
|
||||||
ContextMenuSeparator,
|
|
||||||
ContextMenuShortcut,
|
|
||||||
ContextMenuSub,
|
|
||||||
ContextMenuSubContent,
|
|
||||||
ContextMenuSubTrigger,
|
|
||||||
ContextMenuTrigger,
|
|
||||||
} from "@/registry/new-york-v4/ui/context-menu"
|
|
||||||
|
|
||||||
export function ContextMenuDemo() {
|
|
||||||
return (
|
|
||||||
<ContextMenu>
|
|
||||||
<ContextMenuTrigger className="flex h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed text-sm">
|
|
||||||
Right click here
|
|
||||||
</ContextMenuTrigger>
|
|
||||||
<ContextMenuContent className="w-64">
|
|
||||||
<ContextMenuItem inset>
|
|
||||||
Back
|
|
||||||
<ContextMenuShortcut>⌘[</ContextMenuShortcut>
|
|
||||||
</ContextMenuItem>
|
|
||||||
<ContextMenuItem inset disabled>
|
|
||||||
Forward
|
|
||||||
<ContextMenuShortcut>⌘]</ContextMenuShortcut>
|
|
||||||
</ContextMenuItem>
|
|
||||||
<ContextMenuItem inset>
|
|
||||||
Reload
|
|
||||||
<ContextMenuShortcut>⌘R</ContextMenuShortcut>
|
|
||||||
</ContextMenuItem>
|
|
||||||
<ContextMenuSub>
|
|
||||||
<ContextMenuSubTrigger inset>More Tools</ContextMenuSubTrigger>
|
|
||||||
<ContextMenuSubContent className="w-48">
|
|
||||||
<ContextMenuItem inset>
|
|
||||||
Save Page...
|
|
||||||
<ContextMenuShortcut>⇧⌘S</ContextMenuShortcut>
|
|
||||||
</ContextMenuItem>
|
|
||||||
<ContextMenuItem>
|
|
||||||
<PlusIcon />
|
|
||||||
Create Shortcut...
|
|
||||||
</ContextMenuItem>
|
|
||||||
<ContextMenuItem inset>Name Window...</ContextMenuItem>
|
|
||||||
<ContextMenuSeparator />
|
|
||||||
<ContextMenuItem>
|
|
||||||
<Code2Icon />
|
|
||||||
Developer Tools
|
|
||||||
</ContextMenuItem>
|
|
||||||
<ContextMenuSeparator />
|
|
||||||
<ContextMenuItem variant="destructive">
|
|
||||||
<TrashIcon />
|
|
||||||
Delete
|
|
||||||
</ContextMenuItem>
|
|
||||||
</ContextMenuSubContent>
|
|
||||||
</ContextMenuSub>
|
|
||||||
<ContextMenuSeparator />
|
|
||||||
<ContextMenuCheckboxItem checked>
|
|
||||||
Show Bookmarks Bar
|
|
||||||
<ContextMenuShortcut>⌘⇧B</ContextMenuShortcut>
|
|
||||||
</ContextMenuCheckboxItem>
|
|
||||||
<ContextMenuCheckboxItem>Show Full URLs</ContextMenuCheckboxItem>
|
|
||||||
<ContextMenuSeparator />
|
|
||||||
<ContextMenuRadioGroup value="pedro">
|
|
||||||
<ContextMenuLabel inset>People</ContextMenuLabel>
|
|
||||||
<ContextMenuRadioItem value="pedro">
|
|
||||||
Pedro Duarte
|
|
||||||
</ContextMenuRadioItem>
|
|
||||||
<ContextMenuRadioItem value="colm">Colm Tuite</ContextMenuRadioItem>
|
|
||||||
</ContextMenuRadioGroup>
|
|
||||||
</ContextMenuContent>
|
|
||||||
</ContextMenu>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import { addDays, format } from "date-fns"
|
|
||||||
import { CalendarIcon } from "lucide-react"
|
|
||||||
import { DateRange } from "react-day-picker"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
|
||||||
import { Calendar } from "@/registry/new-york-v4/ui/calendar"
|
|
||||||
import {
|
|
||||||
Popover,
|
|
||||||
PopoverContent,
|
|
||||||
PopoverTrigger,
|
|
||||||
} from "@/registry/new-york-v4/ui/popover"
|
|
||||||
|
|
||||||
export function DatePickerDemo() {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col items-start gap-4 md:flex-row">
|
|
||||||
<DatePickerSimple />
|
|
||||||
<DatePickerWithRange />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function DatePickerSimple() {
|
|
||||||
const [date, setDate] = React.useState<Date>()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Popover>
|
|
||||||
<PopoverTrigger asChild>
|
|
||||||
<Button
|
|
||||||
variant={"outline"}
|
|
||||||
className={cn(
|
|
||||||
"min-w-[200px] justify-start px-2 font-normal",
|
|
||||||
!date && "text-muted-foreground"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<CalendarIcon className="text-muted-foreground" />
|
|
||||||
{date ? format(date, "PPP") : <span>Pick a date</span>}
|
|
||||||
</Button>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent className="w-auto p-0" align="start">
|
|
||||||
<Calendar
|
|
||||||
mode="single"
|
|
||||||
selected={date}
|
|
||||||
onSelect={setDate}
|
|
||||||
initialFocus
|
|
||||||
/>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function DatePickerWithRange() {
|
|
||||||
const [date, setDate] = React.useState<DateRange | undefined>({
|
|
||||||
from: new Date(new Date().getFullYear(), 0, 20),
|
|
||||||
to: addDays(new Date(new Date().getFullYear(), 0, 20), 20),
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Popover>
|
|
||||||
<PopoverTrigger asChild>
|
|
||||||
<Button
|
|
||||||
id="date"
|
|
||||||
variant={"outline"}
|
|
||||||
className={cn(
|
|
||||||
"w-fit justify-start px-2 font-normal",
|
|
||||||
!date && "text-muted-foreground"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<CalendarIcon className="text-muted-foreground" />
|
|
||||||
{date?.from ? (
|
|
||||||
date.to ? (
|
|
||||||
<>
|
|
||||||
{format(date.from, "LLL dd, y")} -{" "}
|
|
||||||
{format(date.to, "LLL dd, y")}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
format(date.from, "LLL dd, y")
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<span>Pick a date</span>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent className="w-auto p-0" align="start">
|
|
||||||
<Calendar
|
|
||||||
initialFocus
|
|
||||||
mode="range"
|
|
||||||
defaultMonth={date?.from}
|
|
||||||
selected={date}
|
|
||||||
onSelect={setDate}
|
|
||||||
numberOfMonths={2}
|
|
||||||
/>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
import { Button } from "@/registry/new-york-v4/ui/button"
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogClose,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogFooter,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "@/registry/new-york-v4/ui/dialog"
|
|
||||||
import { Input } from "@/registry/new-york-v4/ui/input"
|
|
||||||
import { Label } from "@/registry/new-york-v4/ui/label"
|
|
||||||
|
|
||||||
export function DialogDemo() {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col items-start gap-4 md:flex-row">
|
|
||||||
<DialogWithForm />
|
|
||||||
<DialogScrollableContent />
|
|
||||||
<DialogWithStickyFooter />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function DialogWithForm() {
|
|
||||||
return (
|
|
||||||
<Dialog>
|
|
||||||
<form>
|
|
||||||
<DialogTrigger asChild>
|
|
||||||
<Button variant="outline">Edit Profile</Button>
|
|
||||||
</DialogTrigger>
|
|
||||||
<DialogContent className="sm:max-w-[425px]">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Edit profile</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
Make changes to your profile here. Click save when you're
|
|
||||||
done.
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<div className="grid gap-4">
|
|
||||||
<div className="grid gap-3">
|
|
||||||
<Label htmlFor="name-1">Name</Label>
|
|
||||||
<Input id="name-1" name="name" defaultValue="Pedro Duarte" />
|
|
||||||
</div>
|
|
||||||
<div className="grid gap-3">
|
|
||||||
<Label htmlFor="username-1">Username</Label>
|
|
||||||
<Input id="username-1" name="username" defaultValue="@peduarte" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<DialogFooter>
|
|
||||||
<DialogClose asChild>
|
|
||||||
<Button variant="outline">Cancel</Button>
|
|
||||||
</DialogClose>
|
|
||||||
<Button type="submit">Save changes</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</form>
|
|
||||||
</Dialog>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function DialogScrollableContent() {
|
|
||||||
return (
|
|
||||||
<Dialog>
|
|
||||||
<DialogTrigger asChild>
|
|
||||||
<Button variant="outline">Scrollable Content</Button>
|
|
||||||
</DialogTrigger>
|
|
||||||
<DialogContent className="sm:max-w-[425px]">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Scrollable Content</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
This is a dialog with scrollable content.
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<div className="-mx-6 max-h-[500px] overflow-y-auto px-6 text-sm">
|
|
||||||
<h4 className="mb-4 text-lg leading-none font-medium">Lorem Ipsum</h4>
|
|
||||||
{Array.from({ length: 10 }).map((_, index) => (
|
|
||||||
<p key={index} className="mb-4 leading-normal">
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
|
|
||||||
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
|
|
||||||
enim ad minim veniam, quis nostrud exercitation ullamco laboris
|
|
||||||
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
|
|
||||||
reprehenderit in voluptate velit esse cillum dolore eu fugiat
|
|
||||||
nulla pariatur. Excepteur sint occaecat cupidatat non proident,
|
|
||||||
sunt in culpa qui officia deserunt mollit anim id est laborum.
|
|
||||||
</p>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function DialogWithStickyFooter() {
|
|
||||||
return (
|
|
||||||
<Dialog>
|
|
||||||
<DialogTrigger asChild>
|
|
||||||
<Button variant="outline">Sticky Footer</Button>
|
|
||||||
</DialogTrigger>
|
|
||||||
<DialogContent className="sm:max-w-lg">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Scrollable Content</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
This is a dialog with scrollable content.
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<div className="-mx-6 max-h-[500px] overflow-y-auto px-6 text-sm">
|
|
||||||
<h4 className="mb-4 text-lg leading-none font-medium">Lorem Ipsum</h4>
|
|
||||||
{Array.from({ length: 10 }).map((_, index) => (
|
|
||||||
<p key={index} className="mb-4 leading-normal">
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
|
|
||||||
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
|
|
||||||
enim ad minim veniam, quis nostrud exercitation ullamco laboris
|
|
||||||
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
|
|
||||||
reprehenderit in voluptate velit esse cillum dolore eu fugiat
|
|
||||||
nulla pariatur. Excepteur sint occaecat cupidatat non proident,
|
|
||||||
sunt in culpa qui officia deserunt mollit anim id est laborum.
|
|
||||||
</p>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<DialogFooter>
|
|
||||||
<DialogClose asChild>
|
|
||||||
<Button variant="outline">Close</Button>
|
|
||||||
</DialogClose>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user