├── .eslintrc.json
├── .gitignore
├── README.md
├── app
├── favicon.ico
├── globals.css
├── layout.tsx
└── page.tsx
├── components.json
├── components
├── battlefield-chart.tsx
└── ui
│ ├── card.tsx
│ └── chart.tsx
├── lib
└── utils.ts
├── next.config.mjs
├── package.json
├── pnpm-lock.yaml
├── postcss.config.mjs
├── public
├── next.svg
└── vercel.svg
├── tailwind.config.ts
└── tsconfig.json
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Source https://twitter.com/MozzyFX/status/1813154329416568966
2 |
3 |
4 | ```tsx
5 | "use client"
6 |
7 | import { Bar, BarChart, Rectangle, XAxis, YAxis } from "recharts"
8 |
9 | import {
10 | Card,
11 | CardContent,
12 | CardDescription,
13 | CardHeader,
14 | CardTitle,
15 | } from "@/components/ui/card"
16 | import {
17 | ChartConfig,
18 | ChartContainer,
19 | ChartTooltip,
20 | ChartTooltipContent,
21 | } from "@/components/ui/chart"
22 |
23 | const chartData = [
24 | {
25 | name: "Battlefield V",
26 | count: 76986,
27 | percentage: 34.9,
28 | },
29 | {
30 | name: "Battlefield 2042",
31 | count: 54713,
32 | percentage: 24.8,
33 | },
34 | {
35 | name: "Battlefield 4",
36 | count: 40440,
37 | percentage: 18.3,
38 | },
39 | {
40 | name: "Battlefield 1",
41 | count: 35550,
42 | percentage: 16.1,
43 | },
44 | {
45 | name: "Battlefield 3",
46 | count: 5612,
47 | percentage: 2.5,
48 | },
49 | ]
50 |
51 | const chartConfig = {
52 | count: {
53 | label: "Count",
54 | color: "hsl(var(--chart-1))",
55 | },
56 | } satisfies ChartConfig
57 |
58 | export function BattleFieldChart() {
59 | return (
60 |
61 |
62 | Per Game
63 | Since May 25, 2011
64 |
65 |
66 |
67 |
77 |
78 |
79 | (
86 | <>
87 | {/* Draw the bar first. */}
88 |
89 | {/* Places the name at the start */}
90 |
95 | {props.name}
96 |
97 | {/* Places the count at the end */}
98 |
104 | {props.count.toLocaleString()} (
105 | {props.percentage.toFixed(1)}%)
106 |
107 | >
108 | )}
109 | />
110 | }
113 | />
114 |
115 |
116 |
117 |
118 | )
119 | }
120 | ```
121 |
--------------------------------------------------------------------------------
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shadcn/example-battlefield-chart/c79470ef1dc2f37fe27b9dffc17eb207aadd6bcf/app/favicon.ico
--------------------------------------------------------------------------------
/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer base {
6 | :root {
7 | --background: 0 0% 100%;
8 | --foreground: 240 10% 3.9%;
9 | --card: 0 0% 100%;
10 | --card-foreground: 240 10% 3.9%;
11 | --popover: 0 0% 100%;
12 | --popover-foreground: 240 10% 3.9%;
13 | --primary: 240 5.9% 10%;
14 | --primary-foreground: 0 0% 98%;
15 | --secondary: 240 4.8% 95.9%;
16 | --secondary-foreground: 240 5.9% 10%;
17 | --muted: 240 4.8% 95.9%;
18 | --muted-foreground: 240 3.8% 46.1%;
19 | --accent: 240 4.8% 95.9%;
20 | --accent-foreground: 240 5.9% 10%;
21 | --destructive: 0 84.2% 60.2%;
22 | --destructive-foreground: 0 0% 98%;
23 | --border: 240 5.9% 90%;
24 | --input: 240 5.9% 90%;
25 | --ring: 240 10% 3.9%;
26 | --radius: 0.5rem;
27 | --chart-1: 12 76% 61%;
28 | --chart-2: 173 58% 39%;
29 | --chart-3: 197 37% 24%;
30 | --chart-4: 43 74% 66%;
31 | --chart-5: 27 87% 67%;
32 | }
33 |
34 | .dark {
35 | --background: 240 10% 3.9%;
36 | --foreground: 0 0% 98%;
37 | --card: 240 10% 3.9%;
38 | --card-foreground: 0 0% 98%;
39 | --popover: 240 10% 3.9%;
40 | --popover-foreground: 0 0% 98%;
41 | --primary: 0 0% 98%;
42 | --primary-foreground: 240 5.9% 10%;
43 | --secondary: 240 3.7% 15.9%;
44 | --secondary-foreground: 0 0% 98%;
45 | --muted: 240 3.7% 15.9%;
46 | --muted-foreground: 240 5% 64.9%;
47 | --accent: 240 3.7% 15.9%;
48 | --accent-foreground: 0 0% 98%;
49 | --destructive: 0 62.8% 30.6%;
50 | --destructive-foreground: 0 0% 98%;
51 | --border: 240 3.7% 15.9%;
52 | --input: 240 3.7% 15.9%;
53 | --ring: 240 4.9% 83.9%;
54 | --chart-1: 220 70% 50%;
55 | --chart-2: 160 60% 45%;
56 | --chart-3: 30 80% 55%;
57 | --chart-4: 280 65% 60%;
58 | --chart-5: 340 75% 55%;
59 | }
60 | }
61 |
62 | @layer base {
63 | * {
64 | @apply border-border;
65 | }
66 | body {
67 | @apply bg-background text-foreground;
68 | }
69 | }
--------------------------------------------------------------------------------
/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next"
2 | import { Inter } from "next/font/google"
3 | import "./globals.css"
4 |
5 | const inter = Inter({ subsets: ["latin"] })
6 |
7 | export const metadata: Metadata = {
8 | title: "Create Next App",
9 | description: "Generated by create next app",
10 | }
11 |
12 | export default function RootLayout({
13 | children,
14 | }: Readonly<{
15 | children: React.ReactNode
16 | }>) {
17 | return (
18 |
19 |
{children}
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/app/page.tsx:
--------------------------------------------------------------------------------
1 | import { BattleFieldChart } from "@/components/battlefield-chart"
2 |
3 | export default function Home() {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "new-york",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.ts",
8 | "css": "app/globals.css",
9 | "baseColor": "zinc",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils"
16 | }
17 | }
--------------------------------------------------------------------------------
/components/battlefield-chart.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { Bar, BarChart, Rectangle, XAxis, YAxis } from "recharts"
4 |
5 | import {
6 | Card,
7 | CardContent,
8 | CardDescription,
9 | CardHeader,
10 | CardTitle,
11 | } from "@/components/ui/card"
12 | import {
13 | ChartConfig,
14 | ChartContainer,
15 | ChartTooltip,
16 | ChartTooltipContent,
17 | } from "@/components/ui/chart"
18 |
19 | const chartData = [
20 | {
21 | name: "Battlefield V",
22 | count: 76986,
23 | percentage: 34.9,
24 | },
25 | {
26 | name: "Battlefield 2042",
27 | count: 54713,
28 | percentage: 24.8,
29 | },
30 | {
31 | name: "Battlefield 4",
32 | count: 40440,
33 | percentage: 18.3,
34 | },
35 | {
36 | name: "Battlefield 1",
37 | count: 35550,
38 | percentage: 16.1,
39 | },
40 | {
41 | name: "Battlefield 3",
42 | count: 5612,
43 | percentage: 2.5,
44 | },
45 | ]
46 |
47 | const chartConfig = {
48 | count: {
49 | label: "Count",
50 | color: "hsl(var(--chart-1))",
51 | },
52 | } satisfies ChartConfig
53 |
54 | export function BattleFieldChart() {
55 | return (
56 |
57 |
58 | Per Game
59 | Since May 25, 2011
60 |
61 |
62 |
63 |
73 |
74 |
75 | (
82 | <>
83 | {/* Draw the bar first. */}
84 |
85 | {/* Places the name at the start */}
86 |
91 | {props.name}
92 |
93 | {/* Places the count at the end */}
94 |
100 | {props.count.toLocaleString()} (
101 | {props.percentage.toFixed(1)}%)
102 |
103 | >
104 | )}
105 | />
106 | }
109 | />
110 |
111 |
112 |
113 |
114 | )
115 | }
116 |
--------------------------------------------------------------------------------
/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Card = React.forwardRef<
6 | HTMLDivElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
17 | ))
18 | Card.displayName = "Card"
19 |
20 | const CardHeader = React.forwardRef<
21 | HTMLDivElement,
22 | React.HTMLAttributes
23 | >(({ className, ...props }, ref) => (
24 |
29 | ))
30 | CardHeader.displayName = "CardHeader"
31 |
32 | const CardTitle = React.forwardRef<
33 | HTMLParagraphElement,
34 | React.HTMLAttributes
35 | >(({ className, ...props }, ref) => (
36 |
41 | ))
42 | CardTitle.displayName = "CardTitle"
43 |
44 | const CardDescription = React.forwardRef<
45 | HTMLParagraphElement,
46 | React.HTMLAttributes
47 | >(({ className, ...props }, ref) => (
48 |
53 | ))
54 | CardDescription.displayName = "CardDescription"
55 |
56 | const CardContent = React.forwardRef<
57 | HTMLDivElement,
58 | React.HTMLAttributes
59 | >(({ className, ...props }, ref) => (
60 |
61 | ))
62 | CardContent.displayName = "CardContent"
63 |
64 | const CardFooter = React.forwardRef<
65 | HTMLDivElement,
66 | React.HTMLAttributes
67 | >(({ className, ...props }, ref) => (
68 |
73 | ))
74 | CardFooter.displayName = "CardFooter"
75 |
76 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
77 |
--------------------------------------------------------------------------------
/components/ui/chart.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as RechartsPrimitive from "recharts"
5 | import {
6 | NameType,
7 | Payload,
8 | ValueType,
9 | } from "recharts/types/component/DefaultTooltipContent"
10 |
11 | import { cn } from "@/lib/utils"
12 |
13 | // Format: { THEME_NAME: CSS_SELECTOR }
14 | const THEMES = { light: "", dark: ".dark" } as const
15 |
16 | export type ChartConfig = {
17 | [k in string]: {
18 | label?: React.ReactNode
19 | icon?: React.ComponentType
20 | } & (
21 | | { color?: string; theme?: never }
22 | | { color?: never; theme: Record }
23 | )
24 | }
25 |
26 | type ChartContextProps = {
27 | config: ChartConfig
28 | }
29 |
30 | const ChartContext = React.createContext(null)
31 |
32 | function useChart() {
33 | const context = React.useContext(ChartContext)
34 |
35 | if (!context) {
36 | throw new Error("useChart must be used within a ")
37 | }
38 |
39 | return context
40 | }
41 |
42 | const ChartContainer = React.forwardRef<
43 | HTMLDivElement,
44 | React.ComponentProps<"div"> & {
45 | config: ChartConfig
46 | children: React.ComponentProps<
47 | typeof RechartsPrimitive.ResponsiveContainer
48 | >["children"]
49 | }
50 | >(({ id, className, children, config, ...props }, ref) => {
51 | const uniqueId = React.useId()
52 | const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
53 |
54 | return (
55 |
56 |
65 |
66 |
67 | {children}
68 |
69 |
70 |
71 | )
72 | })
73 | ChartContainer.displayName = "Chart"
74 |
75 | const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
76 | const colorConfig = Object.entries(config).filter(
77 | ([_, config]) => config.theme || config.color
78 | )
79 |
80 | if (!colorConfig.length) {
81 | return null
82 | }
83 |
84 | return (
85 |