Skip to content

Commit

Permalink
feat(contribution): support year
Browse files Browse the repository at this point in the history
  • Loading branch information
qianmoQ committed Dec 26, 2024
1 parent c3b033b commit 02a238b
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 56 deletions.
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@

All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.

### 2024.5.4

#### Core

- fix: fix ci scope causing publishing failure

#### Contribution

- feat: support tooltip
- feat: support showLegend
- feat: support showWeek
- feat: support showMonth
- feat: support cellSize and cellGap
- feat: support year
- feat: support custom cell

### 2024.5.3 (2024-12-23)

#### Core
Expand Down
23 changes: 22 additions & 1 deletion docs/components/view/contribution.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,26 @@ This document describes the features and usage of the ShadcnContribution compone

:::

## Year

::: raw

<CodeRunner title="Year">
<ShadcnContribution :data="data" :year="2023" />
</CodeRunner>

:::

::: details Show code

```vue
<template>
<ShadcnContribution :data="data" :year="2023" />
</template>
```

:::

## Contribution Props

<ApiTable title="Props"
Expand All @@ -184,7 +204,8 @@ This document describes the features and usage of the ShadcnContribution compone
['showWeek', 'showWeek value', 'boolean', 'true', '-'],
['showMonth', 'showMonth value', 'boolean', 'true', '-'],
['cellSize', 'cellSize value', 'number', '16', '-'],
['cellGap', 'cellGap value', 'number', '4', '-']
['cellGap', 'cellGap value', 'number', '4', '-'],
['year', 'year value', 'number', '-', '-']
]">
</ApiTable>

Expand Down
7 changes: 6 additions & 1 deletion src/App.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<div class="p-4 min-h-screen">
<div class="p-4 min-h-screen space-y-6">
<h2 class="text-xl font-semibold mb-4">Contribution Graph</h2>
<ShadcnContribution :data="contributionData"
:color-scheme="customColorScheme"
Expand All @@ -18,6 +18,11 @@
</template>
</ShadcnContribution>

<ShadcnContribution :data="contributionData"
:color-scheme="customColorScheme"
:year="2025"
@on-select="handleSelect"/>

<div class="mt-4">
Selected: {{ selectedDate }} - {{ selectedCount }} contributions
</div>
Expand Down
166 changes: 112 additions & 54 deletions src/ui/contribution/ShadcnContribution.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,40 +27,48 @@
</div>
</div>

<div class="grid grid-rows-7 grid-flow-col"
:style="`gap: ${cellGap}px`">
<div v-for="(item, index) in contributionData"
:key="index"
:class="['relative cursor-pointer transition-all duration-200',
<div class="grid grid-flow-col gap-1">
<template v-for="(col, _colIndex) in contributionCols" :key="_colIndex">
<div class="grid grid-rows-7 gap-1">
<div v-for="(item, rowIndex) in col"
:key="rowIndex"
:class="['relative cursor-pointer transition-all duration-200',
'hover:scale-125 hover:z-10']"
:style="{
width: `${cellSize}px`,
height: `${cellSize}px`
}"
@mouseenter="showTooltip($event, item)"
@mouseleave="hideTooltip"
@click="onSelect(item)">
<slot name="cell"
:item="item"
:color="getColor(item.count)">
<div class="w-full h-full rounded-sm"
:style="{ backgroundColor: getColor(item.count) }"/>
</slot>

<div v-if="activeTooltip?.date === item.date"
class="absolute z-50 p-2 text-xs text-white bg-gray-800 rounded whitespace-nowrap transition-all duration-200"
style="bottom: 100%; left: 50%; transform: translateX(-50%); margin-bottom: 4px;">
<div class="mb-2">
<div class="font-medium">{{ formatDate(item.date) }}</div>
<div>{{ item.count }} contributions</div>
:style="{
width: `${cellSize}px`,
height: `${cellSize}px`
}"
@mouseenter="showTooltip($event, item as any)"
@mouseleave="hideTooltip"
@click="onSelect(item as any)">
<template v-if="item">
<slot name="cell"
:item="item as any"
:color="getColor((item as any).count)">
<div class="w-full h-full rounded-sm"
:style="{ backgroundColor: getColor((item as any).count) }"/>
</slot>

<div v-if="activeTooltip?.date === (item as any).date"
class="absolute z-20 p-2 text-xs text-white bg-gray-800 rounded whitespace-nowrap transition-all duration-200"
style="bottom: 100%; left: 50%; transform: translateX(-50%); margin-bottom: 4px;">
<div class="flex flex-col space-y-1">
<div>{{ formatDate((item as any).date) }}</div>
<div>{{ (item as any).count }} {{ t('contribution.text.contribution') }}</div>
</div>
</div>
</template>
<template v-else>
<div class="w-full h-full rounded-sm"/>
</template>
</div>
</div>
</div>
</template>
</div>

<div v-if="showLegend" class="flex items-center gap-2 mt-2 text-xs text-gray-500 justify-end">
<span>{{ t('contribution.text.less') }}</span>
<div class="flex gap-1 relative"
<div class="flex gap-1 relative group"
@mouseenter="showLegendTooltip"
@mouseleave="hideLegendTooltip">
<div v-for="(color, index) in props.colorScheme"
Expand All @@ -73,8 +81,8 @@
class="rounded-sm cursor-help">
</div>

<div v-if="showLegendDetail"
class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 p-2 bg-gray-800 rounded text-white whitespace-nowrap z-50 transition-all duration-200">
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 p-2 bg-gray-800 rounded text-white whitespace-nowrap z-50 transition-opacity duration-200"
:class="[showLegendDetail ? 'visible opacity-100' : 'invisible opacity-0']">
<div class="flex flex-col gap-1.5">
<div v-for="(range, index) in contributionRanges"
:key="index"
Expand Down Expand Up @@ -118,36 +126,82 @@ const emit = defineEmits<ContributionEmits>()
const MONTH_LABEL_HEIGHT = 20
// Get last N years of dates
// 获取过去N年的日期
// Get contribution data for the year
const contributionData = computed(() => {
const endDate = new Date()
const startDate = new Date()
startDate.setFullYear(endDate.getFullYear() - props.yearCount)
const dates = [] as any[]
const currentDate = new Date(startDate)
// Adjust start date to previous Sunday
// 调整开始日期到上一个周日
const day = currentDate.getDay()
currentDate.setDate(currentDate.getDate() - day)
if (props.year !== undefined) {
const startDate = new Date(Date.UTC(props.year, 0, 1))
const endDate = new Date(Date.UTC(props.year, 11, 31))
const currentDate = new Date(startDate)
console.log('Start Date UTC:', startDate.toISOString())
console.log('Start Date Local:', startDate.toLocaleDateString(), 'Weekday:', startDate.getDay())
while (currentDate <= endDate) {
const dateStr = currentDate.toISOString().split('T')[0]
const existingData = props.data.find(d => d.date === dateStr)
console.log('Starting date:', startDate.toISOString(), 'Day:', startDate.getDay())
dates.push({
date: dateStr,
count: existingData ? existingData.count : 0
})
while (currentDate <= endDate) {
const dateStr = currentDate.toISOString().split('T')[0]
const weekday = currentDate.getDay()
const existingData = props.data.find(d => d.date === dateStr)
currentDate.setDate(currentDate.getDate() + 1)
dates.push({
date: dateStr,
count: existingData ? existingData.count : 0,
weekday: weekday
})
currentDate.setDate(currentDate.getDate() + 1)
}
}
else {
const endDate = new Date()
const startDate = new Date()
startDate.setFullYear(endDate.getFullYear() - props.yearCount)
const currentDate = new Date(startDate)
while (currentDate <= endDate) {
const dateStr = currentDate.toISOString().split('T')[0]
const existingData = props.data.find(d => d.date === dateStr)
dates.push({
date: dateStr,
count: existingData ? existingData.count : 0,
weekday: currentDate.getDay()
})
currentDate.setDate(currentDate.getDate() + 1)
}
}
return dates
})
// Organize data into columns (weeks)
const contributionCols = computed(() => {
const data = contributionData.value
const cols = [] as any[]
let currentCol = Array(7).fill(null)
let weekStartIndex = data[0]?.weekday || 0
let colIndex = 0
for (let i = 0; i < weekStartIndex; i++) {
currentCol[i] = null
}
data.forEach((item, index) => {
currentCol[item.weekday] = item
if (item.weekday === 6 || index === data.length - 1) {
cols.push([...currentCol])
currentCol = Array(7).fill(null)
colIndex++
}
})
return cols
})
const monthLabels = computed(() => {
const monthNames = [
t('datePicker.text.january'),
Expand Down Expand Up @@ -187,9 +241,8 @@ const monthLabels = computed(() => {
})
// Get color based on contribution count
// 根据贡献数量获取颜色
const getColor = (count: number) => {
if (count === 0) {
const getColor = (count: number | undefined) => {
if (count === undefined || count === null || count === 0) {
return props.colorScheme[0]
}
if (count <= 3) {
Expand All @@ -205,13 +258,17 @@ const getColor = (count: number) => {
}
const onSelect = (item: ContributionOption) => {
emit('on-select', item)
if (item) {
emit('on-select', item)
}
}
const activeTooltip = ref<ContributionOption | null>(null)
const showTooltip = (_event: MouseEvent, item: ContributionOption) => {
activeTooltip.value = item
if (item) {
activeTooltip.value = item
}
}
const hideTooltip = () => {
Expand All @@ -222,7 +279,8 @@ const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleDateString(undefined, {
year: 'numeric',
month: 'long',
day: 'numeric'
day: 'numeric',
weekday: 'long'
})
}
Expand Down
2 changes: 2 additions & 0 deletions src/ui/contribution/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export interface ContributionOption
{
date: string
count: number
weekday: number
}

export interface ContributionProps
Expand All @@ -14,6 +15,7 @@ export interface ContributionProps
showMonth?: boolean
cellSize?: number
cellGap?: number
year?: number
}

export type ContributionEmits = {
Expand Down

0 comments on commit 02a238b

Please sign in to comment.