├── .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 |