Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions src/components/DiffSummary/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Meta, StoryObj } from '@storybook/react'
import DiffSummary from '.'

const meta: Meta<typeof DiffSummary> = {
component: DiffSummary,
}

export default meta

type Story = StoryObj<typeof DiffSummary>

export const Default: Story = {
args: {
addedLines: 10,
removedLines: 5,
squareCount: 5,
},
}

export const ManyLineChanges: Story = {
args: {
addedLines: 1500,
removedLines: 2050,
squareCount: 5,
},
}

export const NoChanges: Story = {
args: {
addedLines: 0,
removedLines: 0,
squareCount: 5,
},
}

export const NoLabel: Story = {
args: {
addedLines: 10,
removedLines: 5,
squareCount: 5,
showLabel: false,
},
}

export const CustomSquareCount: Story = {
args: {
addedLines: 10,
removedLines: 5,
squareCount: 10,
},
}

export const CustomSquareClassName: Story = {
args: {
addedLines: 10,
removedLines: 5,
squareCount: 5,
squareClassName: 'size-4',
},
}
81 changes: 81 additions & 0 deletions src/components/DiffSummary/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { cn } from '@/lib/utils'

interface DiffSummaryProps {
addedLines: number
removedLines: number
squareCount?: number
squareClassName?: string
showLabel?: boolean
}

export default function DiffSummary({
addedLines,
removedLines,
squareCount = 5,
squareClassName,
showLabel = true,
}: DiffSummaryProps) {
const totalLines = addedLines + removedLines
const filledAddedSquares =
totalLines > 0 ? Math.round((addedLines / totalLines) * squareCount) : 0
const filledRemovedSquares =
totalLines > 0 ? Math.round((removedLines / totalLines) * squareCount) : 0

// Ensure the total does not exceed squareCount
let greenCount = filledAddedSquares
let redCount = filledRemovedSquares
if (greenCount + redCount > squareCount) {
const overflow = greenCount + redCount - squareCount
if (greenCount > redCount) {
greenCount -= overflow
} else {
redCount -= overflow
}
}

const baseSquareClasses = cn('w-3.5 h-3.5 rounded-sm', squareClassName)

const squares = Array.from({ length: squareCount }, (_, index) => {
if (index < greenCount) {
return (
<div
key={index}
className={cn(
baseSquareClasses,
'bg-emerald-500 dark:bg-emerald-400'
)}
/>
)
} else if (index < greenCount + redCount) {
return (
<div
key={index}
className={cn(baseSquareClasses, 'bg-rose-500 dark:bg-rose-400')}
/>
)
} else {
return (
<div
key={index}
className={cn(baseSquareClasses, 'bg-gray-200 dark:bg-gray-200')}
/>
)
}
})

return (
<div className="flex items-center gap-1.5">
{showLabel && (
<span className="flex flex-row gap-1.5 text-sm font-semibold">
<span className="text-emerald-500 dark:text-emerald-400">
+{addedLines}
</span>
<span className="text-rose-500 dark:text-rose-400">
-{removedLines}
</span>
</span>
)}
<div className="flex items-center gap-0.5">{squares}</div>
</div>
)
}