@gnome-ui/react 1.43.0 → 1.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/components/ContributionGraph/ContributionGraph.cjs +1 -1
- package/dist/components/ContributionGraph/ContributionGraph.cjs.map +1 -1
- package/dist/components/ContributionGraph/ContributionGraph.d.ts +11 -6
- package/dist/components/ContributionGraph/ContributionGraph.js +211 -130
- package/dist/components/ContributionGraph/ContributionGraph.js.map +1 -1
- package/dist/components/ContributionGraph/ContributionGraph.module.css.cjs +1 -1
- package/dist/components/ContributionGraph/ContributionGraph.module.css.cjs.map +1 -1
- package/dist/components/ContributionGraph/ContributionGraph.module.css.js +9 -8
- package/dist/components/ContributionGraph/ContributionGraph.module.css.js.map +1 -1
- package/dist/components/ContributionGraph/index.d.ts +1 -1
- package/dist/components/Drawer/Drawer.cjs +2 -0
- package/dist/components/Drawer/Drawer.cjs.map +1 -0
- package/dist/components/Drawer/Drawer.d.ts +28 -0
- package/dist/components/Drawer/Drawer.js +55 -0
- package/dist/components/Drawer/Drawer.js.map +1 -0
- package/dist/components/Drawer/Drawer.module.css.cjs +2 -0
- package/dist/components/Drawer/Drawer.module.css.cjs.map +1 -0
- package/dist/components/Drawer/Drawer.module.css.js +18 -0
- package/dist/components/Drawer/Drawer.module.css.js.map +1 -0
- package/dist/components/Drawer/index.d.ts +2 -0
- package/dist/components/Drawer.cjs +1 -0
- package/dist/components/Drawer.d.ts +2 -0
- package/dist/components/Drawer.js +2 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +64 -63
- package/dist/style.css +1 -1
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -252,6 +252,7 @@ import { Button } from "@gnome-ui/react/components/Button";
|
|
|
252
252
|
| [`Toast`](https://eljijuna.github.io/gnome-ui/?path=/docs/components-toast--docs) / `Toaster` | Non-blocking temporary notification with auto-dismiss, action, and queue support |
|
|
253
253
|
| [`Dialog`](https://eljijuna.github.io/gnome-ui/?path=/docs/components-dialog--docs) | Blocking modal with title, body, focus trap, and configurable buttons |
|
|
254
254
|
| [`BottomSheet`](https://eljijuna.github.io/gnome-ui/?path=/docs/components-bottomsheet--docs) | Slide-up panel from the bottom of the viewport with drag handle and optional title |
|
|
255
|
+
| [`Drawer`](https://eljijuna.github.io/gnome-ui/?path=/docs/components-drawer--docs) | Slide-over panel that opens from the left or right with React content |
|
|
255
256
|
| [`Tooltip`](https://eljijuna.github.io/gnome-ui/?path=/docs/components-tooltip--docs) | Floating informational label on hover/focus with auto-flip positioning |
|
|
256
257
|
| [`Popover`](https://eljijuna.github.io/gnome-ui/?path=/docs/components-popover--docs) | Floating interactive panel with arrow and auto-flip positioning |
|
|
257
258
|
| [`Banner`](https://eljijuna.github.io/gnome-ui/?path=/docs/components-banner--docs) | Persistent message strip with optional action and dismiss |
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require(`./ContributionGraph.module.css.cjs`),t=require(`../GnomeProvider/GnomeContext.cjs`);let n=require(`react`),r=require(`react/jsx-runtime`);var
|
|
1
|
+
const e=require(`./ContributionGraph.module.css.cjs`),t=require(`../GnomeProvider/GnomeContext.cjs`);let n=require(`react`),r=require(`react-dom`),i=require(`react/jsx-runtime`);var a=new Date(2e3,0,2);function o(e,t){return t.format(new Date(2e3,e))}function s(e,t){let n=new Date(a);return n.setDate(a.getDate()+e),t.format(n)}var c=28,l=20,u=new Set([`blue`,`green`,`yellow`,`orange`,`red`,`purple`,`brown`]);function ee(e){return[`var(--gnome-card-shade-color)`,`var(--gnome-${e}-1)`,`var(--gnome-${e}-2)`,`var(--gnome-${e}-4)`,`var(--gnome-${e}-5)`]}function d(e){return`${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,`0`)}-${String(e.getDate()).padStart(2,`0`)}`}function f(e){let[t,n,r]=e.split(`-`).map(Number);return new Date(t,n-1,r)}function p(e,t){return t.format(f(e))}function m({data:a,maxLevel:f=4,cellSize:m=12,cellGap:h=3,responsive:g=!0,minCellSize:te=8,maxCellSize:ne=24,weekStartDay:_=1,showMonthLabels:v=!0,showDayLabels:y=!0,showLegend:re=!0,weeks:b=52,ariaLabel:x=`Contribution graph`,onDayClick:S,tooltipContent:ie,className:ae}){let C=t.useDateTimeFormatter({month:`short`}),w=t.useDateTimeFormatter({weekday:`short`}),T=t.useDateTimeFormatter({weekday:`long`,year:`numeric`,month:`long`,day:`numeric`}),{accentColor:E}=(0,n.useContext)(t.GnomeContext),D=u.has(E)?E:`green`,O=(0,n.useRef)(null),k=(0,n.useRef)(null),A=(0,n.useRef)(null),j=(0,n.useMemo)(()=>ee(D),[D]),[M,N]=(0,n.useState)(null),[P,F]=(0,n.useState)({col:0,row:0}),[I,L]=(0,n.useState)(null),[R,z]=(0,n.useState)(null);(0,n.useEffect)(()=>{if(!g)return;let e=k.current;if(!e)return;let t=()=>{let t=e.clientWidth||e.getBoundingClientRect().width;t>0&&N(t)};if(t(),typeof ResizeObserver>`u`)return window.addEventListener(`resize`,t),()=>window.removeEventListener(`resize`,t);let n=new ResizeObserver(t);return n.observe(e),()=>n.disconnect()},[g]),(0,n.useEffect)(()=>{if(!I)return;let e=()=>{let e=A.current;if(!e)return;let t=I.target.getBoundingClientRect(),n=e.getBoundingClientRect(),r=document.documentElement.clientWidth||window.innerWidth,i=document.documentElement.clientHeight||window.innerHeight,a=Math.max(8,r-n.width-8),o=Math.max(8,i-n.height-8),s=t.left+t.width/2-n.width/2,c=t.top-n.height-6,l=c>=8?c:t.bottom+6;z({left:Math.max(8,Math.min(s,a)),top:Math.max(8,Math.min(l,o))})};return e(),window.addEventListener(`resize`,e),window.addEventListener(`scroll`,e,{passive:!0,capture:!0}),()=>{window.removeEventListener(`resize`,e),window.removeEventListener(`scroll`,e,{capture:!0})}},[I]);let B=(0,n.useMemo)(()=>{let e=new Map;for(let t of a)e.set(t.date,t.count);return e},[a]),V=(0,n.useMemo)(()=>Math.max(1,...a.map(e=>e.count)),[a]),H=y?c:0,U=v?l:0,W=Math.max(1,Math.floor(b)),G=Math.max(1,te),K=Math.max(G,ne),q=(0,n.useMemo)(()=>{if(!g||M===null)return{cellSize:m,weeks:W};let e=Math.max(0,M-H),t=(e+h)/W-h;if(t>=G)return{cellSize:Math.min(K,t),weeks:W};let n=Math.floor((e+h)/(G+h));return{cellSize:G,weeks:Math.max(1,Math.min(W,n))}},[M,h,m,W,H,K,G,g]),J=q.weeks,Y=q.cellSize,X=Y+h,{grid:Z,monthLabels:oe}=(0,n.useMemo)(()=>{let e=new Date;e.setHours(0,0,0,0);let t=(e.getDay()-_+7)%7,n=new Date(e);n.setDate(e.getDate()-t);let r=new Date(n);r.setDate(n.getDate()-(J-1)*7);let i=[],a=[],s=-1;for(let t=0;t<J;t++){let n=new Date(r);n.setDate(r.getDate()+t*7),n.getMonth()!==s&&(a.push({col:t,month:o(n.getMonth(),C)}),s=n.getMonth());let c=[];for(let n=0;n<7;n++){let i=new Date(r);i.setDate(r.getDate()+t*7+n);let a=i>e,o=d(i),s=a?0:B.get(o)??0,l=s===0?0:Math.min(f,Math.ceil(s/V*f));c.push({iso:o,count:s,level:l,future:a})}i.push(c)}return{grid:i,monthLabels:a}},[B,V,f,J,_,C]),se=(0,n.useMemo)(()=>[1,3,5].map(e=>({row:(e-_+7)%7,label:s(e,w)})),[_,w]),ce=H+J*X-h,le=U+7*X-h,Q=Math.min(4,Math.floor(Y/3));(0,n.useEffect)(()=>{F(e=>({col:Math.min(J-1,e.col),row:e.row}))},[J]);function ue(e,t){let n=Math.max(0,Math.min(J-1,e)),r=Math.max(0,Math.min(6,t));F({col:n,row:r}),O.current?.querySelector(`[data-col="${n}"][data-row="${r}"]`)?.focus()}function $(e,t,n){let r={ArrowRight:[t+1,n],ArrowLeft:[t-1,n],ArrowDown:[t,n+1],ArrowUp:[t,n-1]};if(r[e.key])e.preventDefault(),ue(...r[e.key]);else if(e.key===`Enter`||e.key===` `){e.preventDefault();let r=Z[t]?.[n];r&&!r.future&&S?.({date:r.iso,count:r.count})}}function de(e,t){z(null),L({target:e.currentTarget,text:t})}function fe(){L(null),z(null)}function pe(e){return e.future?j[0]:j[Math.min(e.level,j.length-1)]??j[j.length-1]}function me(e){if(e.future)return p(e.iso,T);let t=p(e.iso,T);return ie?.({date:e.iso,count:e.count})??`${e.count} contribution${e.count===1?``:`s`} on ${t}`}return(0,i.jsxs)(`div`,{className:[e.default.wrapper,ae].filter(Boolean).join(` `),children:[(0,i.jsx)(`div`,{ref:k,className:e.default.graphViewport,children:(0,i.jsxs)(`svg`,{ref:O,width:ce,height:le,className:e.default.svg,"aria-label":x,role:`img`,"data-cell-size":Y,"data-visible-weeks":J,children:[v&&oe.map(({col:t,month:n})=>(0,i.jsx)(`text`,{x:H+t*X,y:12,className:e.default.label,children:n},`m-${t}`)),y&&se.map(({row:t,label:n})=>(0,i.jsx)(`text`,{x:0,y:U+t*X+Y-1,className:e.default.label,children:n},`d-${t}`)),(0,i.jsx)(`g`,{role:`grid`,"aria-label":x,children:Z.map((t,n)=>(0,i.jsx)(`g`,{role:`row`,children:t.map((t,r)=>{let a=me(t),o=P.col===n&&P.row===r;return(0,i.jsx)(`rect`,{"data-col":n,"data-row":r,x:H+n*X,y:U+r*X,width:Y,height:Y,rx:Q,fill:pe(t),opacity:t.future?.35:1,className:e.default.cell,role:`gridcell`,"aria-label":a,"aria-disabled":t.future||void 0,tabIndex:o?0:-1,onClick:()=>!t.future&&S?.({date:t.iso,count:t.count}),onKeyDown:e=>$(e,n,r),onFocus:()=>F({col:n,row:r}),onMouseEnter:e=>de(e,a),onMouseLeave:fe},`${n}-${r}`)})},n))})]})}),I&&typeof document<`u`&&(0,r.createPortal)((0,i.jsx)(`div`,{ref:A,className:e.default.tooltip,style:R?{left:R.left,top:R.top}:{visibility:`hidden`,left:0,top:0},role:`tooltip`,children:I.text}),document.body),re&&(0,i.jsxs)(`div`,{className:e.default.legend,children:[(0,i.jsx)(`span`,{className:e.default.legendLabel,children:`Less`}),Array.from({length:f+1},(t,n)=>(0,i.jsx)(`svg`,{width:Y,height:Y,"aria-hidden":`true`,className:e.default.legendCell,children:(0,i.jsx)(`rect`,{width:Y,height:Y,rx:Q,fill:j[Math.min(n,j.length-1)]})},n)),(0,i.jsx)(`span`,{className:e.default.legendLabel,children:`More`})]})]})}exports.ContributionGraph=m;
|
|
2
2
|
//# sourceMappingURL=ContributionGraph.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ContributionGraph.cjs","names":[],"sources":["../../../src/components/ContributionGraph/ContributionGraph.tsx"],"sourcesContent":["import { useMemo, useRef, useState } from \"react\";\nimport type { KeyboardEvent } from \"react\";\nimport styles from \"./ContributionGraph.module.css\";\nimport { useDateTimeFormatter } from \"../GnomeProvider/GnomeContext\";\n\nexport interface ContributionDay {\n /** ISO 8601 date — \"YYYY-MM-DD\". */\n date: string;\n /** Non-negative activity count. */\n count: number;\n}\n\nexport interface ContributionGraphProps {\n /** Activity data. Days absent from the array are treated as count = 0. */\n data: ContributionDay[];\n /**\n * Number of intensity levels (excluding 0).\n * @default 4\n */\n maxLevel?: number;\n /**\n * Colour scale — length must be maxLevel + 1 (index 0 = empty).\n * Defaults to the Adwaita green palette.\n */\n colorScale?: string[];\n /** Cell side length in pixels. @default 12 */\n cellSize?: number;\n /** Gap between cells in pixels. @default 3 */\n cellGap?: number;\n /** 0 = Sunday · 1 = Monday (GNOME locale default). @default 1 */\n weekStartDay?: 0 | 1;\n /** @default true */\n showMonthLabels?: boolean;\n /** @default true */\n showDayLabels?: boolean;\n /** @default true */\n showLegend?: boolean;\n /** Number of week columns to display. @default 52 */\n weeks?: number;\n /** @default \"Contribution graph\" */\n ariaLabel?: string;\n onDayClick?: (day: ContributionDay) => void;\n /** Returns a plain-text tooltip string for a day. */\n tooltipContent?: (day: ContributionDay) => string;\n className?: string;\n}\n\n// Reference Sunday: 2000-01-02 is a Sunday (day index 0)\nconst REF_SUNDAY = new Date(2000, 0, 2);\n\nfunction getShortMonth(\n monthIndex: number,\n formatter: Intl.DateTimeFormat,\n): string {\n return formatter.format(new Date(2000, monthIndex));\n}\n\nfunction getShortDay(\n dayIndex: number,\n formatter: Intl.DateTimeFormat,\n): string {\n const date = new Date(REF_SUNDAY);\n date.setDate(REF_SUNDAY.getDate() + dayIndex);\n return formatter.format(date);\n}\n\nconst DAY_LABEL_WIDTH = 28;\nconst MONTH_LABEL_HEIGHT = 20;\n\nconst DEFAULT_COLORS = [\n \"var(--gnome-card-shade-color, rgba(0,0,0,0.07))\",\n \"var(--gnome-green-1, #8ff0a4)\",\n \"var(--gnome-green-2, #57e389)\",\n \"var(--gnome-green-4, #2ec27e)\",\n \"var(--gnome-green-5, #26a269)\",\n];\n\nfunction dateToIso(date: Date): string {\n const y = date.getFullYear();\n const m = String(date.getMonth() + 1).padStart(2, \"0\");\n const d = String(date.getDate()).padStart(2, \"0\");\n return `${y}-${m}-${d}`;\n}\n\nfunction isoToLocal(iso: string): Date {\n const [y, m, d] = iso.split(\"-\").map(Number);\n return new Date(y, m - 1, d);\n}\n\nfunction fullDateLabel(iso: string, formatter: Intl.DateTimeFormat): string {\n return formatter.format(isoToLocal(iso));\n}\n\ninterface GridCell {\n iso: string;\n count: number;\n level: number;\n future: boolean;\n}\n\nexport function ContributionGraph({\n data,\n maxLevel = 4,\n colorScale,\n cellSize = 12,\n cellGap = 3,\n weekStartDay = 1,\n showMonthLabels = true,\n showDayLabels = true,\n showLegend = true,\n weeks = 52,\n ariaLabel = \"Contribution graph\",\n onDayClick,\n tooltipContent,\n className,\n}: ContributionGraphProps) {\n const monthFormatter = useDateTimeFormatter({ month: \"short\" });\n const weekdayFormatter = useDateTimeFormatter({ weekday: \"short\" });\n const fullDateFormatter = useDateTimeFormatter({\n weekday: \"long\",\n year: \"numeric\",\n month: \"long\",\n day: \"numeric\",\n });\n const stride = cellSize + cellGap;\n const svgRef = useRef<SVGSVGElement>(null);\n const wrapperRef = useRef<HTMLDivElement>(null);\n const colors = colorScale ?? DEFAULT_COLORS;\n\n const [focusedCell, setFocusedCell] = useState({ col: 0, row: 0 });\n const [tooltip, setTooltip] = useState<{\n x: number;\n y: number;\n text: string;\n } | null>(null);\n\n const countMap = useMemo(() => {\n const map = new Map<string, number>();\n for (const d of data) map.set(d.date, d.count);\n return map;\n }, [data]);\n\n const maxCount = useMemo(\n () => Math.max(1, ...data.map((d) => d.count)),\n [data],\n );\n\n const { grid, monthLabels } = useMemo(() => {\n const today = new Date();\n today.setHours(0, 0, 0, 0);\n\n // Align to start of current week\n const daysFromWeekStart = (today.getDay() - weekStartDay + 7) % 7;\n const lastWeekStart = new Date(today);\n lastWeekStart.setDate(today.getDate() - daysFromWeekStart);\n\n const firstDay = new Date(lastWeekStart);\n firstDay.setDate(lastWeekStart.getDate() - (weeks - 1) * 7);\n\n const gridResult: GridCell[][] = [];\n const labels: { col: number; month: string }[] = [];\n let lastMonth = -1;\n\n for (let col = 0; col < weeks; col++) {\n const colStart = new Date(firstDay);\n colStart.setDate(firstDay.getDate() + col * 7);\n\n // Month label when the column starts a new month\n if (colStart.getMonth() !== lastMonth) {\n labels.push({\n col,\n month: getShortMonth(colStart.getMonth(), monthFormatter),\n });\n lastMonth = colStart.getMonth();\n }\n\n const column: GridCell[] = [];\n for (let row = 0; row < 7; row++) {\n const date = new Date(firstDay);\n date.setDate(firstDay.getDate() + col * 7 + row);\n const future = date > today;\n const iso = dateToIso(date);\n const count = future ? 0 : (countMap.get(iso) ?? 0);\n const level =\n count === 0\n ? 0\n : Math.min(maxLevel, Math.ceil((count / maxCount) * maxLevel));\n column.push({ iso, count, level, future });\n }\n gridResult.push(column);\n }\n\n return { grid: gridResult, monthLabels: labels };\n }, [countMap, maxCount, maxLevel, weeks, weekStartDay, monthFormatter]);\n\n // Always label Mon(1), Wed(3), Fri(5) — rows shift with weekStartDay\n const labelRows = useMemo(\n () =>\n [1, 3, 5].map((dayIndex) => ({\n row: (dayIndex - weekStartDay + 7) % 7,\n label: getShortDay(dayIndex, weekdayFormatter),\n })),\n [weekStartDay, weekdayFormatter],\n );\n\n const dayLabelW = showDayLabels ? DAY_LABEL_WIDTH : 0;\n const monthLabelH = showMonthLabels ? MONTH_LABEL_HEIGHT : 0;\n const svgWidth = dayLabelW + weeks * stride - cellGap;\n const svgHeight = monthLabelH + 7 * stride - cellGap;\n const cellRx = Math.min(4, Math.floor(cellSize / 3));\n\n function focusCell(col: number, row: number) {\n const c = Math.max(0, Math.min(weeks - 1, col));\n const r = Math.max(0, Math.min(6, row));\n setFocusedCell({ col: c, row: r });\n svgRef.current\n ?.querySelector<SVGRectElement>(`[data-col=\"${c}\"][data-row=\"${r}\"]`)\n ?.focus();\n }\n\n function handleCellKey(e: KeyboardEvent, col: number, row: number) {\n const moves: Record<string, [number, number]> = {\n ArrowRight: [col + 1, row],\n ArrowLeft: [col - 1, row],\n ArrowDown: [col, row + 1],\n ArrowUp: [col, row - 1],\n };\n if (moves[e.key]) {\n e.preventDefault();\n focusCell(...moves[e.key]);\n } else if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n const cell = grid[col]?.[row];\n if (cell && !cell.future) onDayClick?.({ date: cell.iso, count: cell.count });\n }\n }\n\n function handleMouseEnter(\n e: React.MouseEvent<SVGRectElement>,\n text: string,\n ) {\n const wrapper = wrapperRef.current;\n if (!wrapper) return;\n const wRect = wrapper.getBoundingClientRect();\n const rRect = (e.currentTarget as Element).getBoundingClientRect();\n setTooltip({\n x: rRect.left - wRect.left + cellSize / 2,\n y: rRect.top - wRect.top,\n text,\n });\n }\n\n function cellColor(cell: GridCell): string {\n if (cell.future) return colors[0];\n return colors[Math.min(cell.level, colors.length - 1)] ?? colors[colors.length - 1];\n }\n\n function cellTooltip(cell: GridCell): string {\n if (cell.future) return fullDateLabel(cell.iso, fullDateFormatter);\n const formattedDate = fullDateLabel(cell.iso, fullDateFormatter);\n return (\n tooltipContent?.({ date: cell.iso, count: cell.count }) ??\n `${cell.count} contribution${cell.count !== 1 ? \"s\" : \"\"} on ${formattedDate}`\n );\n }\n\n return (\n <div\n ref={wrapperRef}\n className={[styles.wrapper, className].filter(Boolean).join(\" \")}\n >\n <svg\n ref={svgRef}\n width={svgWidth}\n height={svgHeight}\n className={styles.svg}\n aria-label={ariaLabel}\n role=\"img\"\n >\n {/* Month labels */}\n {showMonthLabels &&\n monthLabels.map(({ col, month }) => (\n <text\n key={`m-${col}`}\n x={dayLabelW + col * stride}\n y={12}\n className={styles.label}\n >\n {month}\n </text>\n ))}\n\n {/* Day-of-week labels: Mon, Wed, Fri */}\n {showDayLabels &&\n labelRows.map(({ row, label }) => (\n <text\n key={`d-${row}`}\n x={0}\n y={monthLabelH + row * stride + cellSize - 1}\n className={styles.label}\n >\n {label}\n </text>\n ))}\n\n {/* Activity grid */}\n <g role=\"grid\" aria-label={ariaLabel}>\n {grid.map((column, col) => (\n <g key={col} role=\"row\">\n {column.map((cell, row) => {\n const tipText = cellTooltip(cell);\n const isFocused =\n focusedCell.col === col && focusedCell.row === row;\n\n return (\n <rect\n key={`${col}-${row}`}\n data-col={col}\n data-row={row}\n x={dayLabelW + col * stride}\n y={monthLabelH + row * stride}\n width={cellSize}\n height={cellSize}\n rx={cellRx}\n fill={cellColor(cell)}\n opacity={cell.future ? 0.35 : 1}\n className={styles.cell}\n role=\"gridcell\"\n aria-label={tipText}\n aria-disabled={cell.future || undefined}\n tabIndex={isFocused ? 0 : -1}\n onClick={() => !cell.future && onDayClick?.({ date: cell.iso, count: cell.count })}\n onKeyDown={(e) => handleCellKey(e, col, row)}\n onFocus={() => setFocusedCell({ col, row })}\n onMouseEnter={(e) => handleMouseEnter(e, tipText)}\n onMouseLeave={() => setTooltip(null)}\n />\n );\n })}\n </g>\n ))}\n </g>\n </svg>\n\n {tooltip && (\n <div\n className={styles.tooltip}\n style={{ left: tooltip.x, top: tooltip.y }}\n role=\"tooltip\"\n >\n {tooltip.text}\n </div>\n )}\n\n {showLegend && (\n <div className={styles.legend}>\n <span className={styles.legendLabel}>Less</span>\n {Array.from({ length: maxLevel + 1 }, (_, i) => (\n <svg\n key={i}\n width={cellSize}\n height={cellSize}\n aria-hidden=\"true\"\n className={styles.legendCell}\n >\n <rect\n width={cellSize}\n height={cellSize}\n rx={cellRx}\n fill={colors[Math.min(i, colors.length - 1)]}\n />\n </svg>\n ))}\n <span className={styles.legendLabel}>More</span>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ContributionGraph.cjs","names":[],"sources":["../../../src/components/ContributionGraph/ContributionGraph.tsx"],"sourcesContent":["import { useContext, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { KeyboardEvent } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport styles from \"./ContributionGraph.module.css\";\nimport {\n GnomeContext,\n useDateTimeFormatter,\n type GnomeNamedAccentColor,\n} from \"../GnomeProvider/GnomeContext\";\n\nexport interface ContributionDay {\n /** ISO 8601 date — \"YYYY-MM-DD\". */\n date: string;\n /** Non-negative activity count. */\n count: number;\n}\n\nexport interface ContributionGraphProps {\n /** Activity data. Days absent from the array are treated as count = 0. */\n data: ContributionDay[];\n /**\n * Number of intensity levels (excluding 0).\n * @default 4\n */\n maxLevel?: number;\n /** Cell side length in pixels. @default 12 */\n cellSize?: number;\n /** Gap between cells in pixels. @default 3 */\n cellGap?: number;\n /**\n * Fit the graph to its container by resizing cells and reducing visible\n * weeks when the configured range cannot stay legible.\n * @default true\n */\n responsive?: boolean;\n /** Smallest responsive cell side length in pixels. @default 8 */\n minCellSize?: number;\n /** Largest responsive cell side length in pixels. @default 24 */\n maxCellSize?: number;\n /** 0 = Sunday · 1 = Monday (GNOME locale default). @default 1 */\n weekStartDay?: 0 | 1;\n /** @default true */\n showMonthLabels?: boolean;\n /** @default true */\n showDayLabels?: boolean;\n /** @default true */\n showLegend?: boolean;\n /** Number of week columns to display. @default 52 */\n weeks?: number;\n /** @default \"Contribution graph\" */\n ariaLabel?: string;\n onDayClick?: (day: ContributionDay) => void;\n /** Returns a plain-text tooltip string for a day. */\n tooltipContent?: (day: ContributionDay) => string;\n className?: string;\n}\n\n// Reference Sunday: 2000-01-02 is a Sunday (day index 0)\nconst REF_SUNDAY = new Date(2000, 0, 2);\n\nfunction getShortMonth(\n monthIndex: number,\n formatter: Intl.DateTimeFormat,\n): string {\n return formatter.format(new Date(2000, monthIndex));\n}\n\nfunction getShortDay(\n dayIndex: number,\n formatter: Intl.DateTimeFormat,\n): string {\n const date = new Date(REF_SUNDAY);\n date.setDate(REF_SUNDAY.getDate() + dayIndex);\n return formatter.format(date);\n}\n\nconst DAY_LABEL_WIDTH = 28;\nconst MONTH_LABEL_HEIGHT = 20;\n\nconst NAMED_ACCENTS = new Set<GnomeNamedAccentColor>([\n \"blue\",\n \"green\",\n \"yellow\",\n \"orange\",\n \"red\",\n \"purple\",\n \"brown\",\n]);\n\nfunction accentScale(accentColor: GnomeNamedAccentColor): string[] {\n return [\n \"var(--gnome-card-shade-color)\",\n `var(--gnome-${accentColor}-1)`,\n `var(--gnome-${accentColor}-2)`,\n `var(--gnome-${accentColor}-4)`,\n `var(--gnome-${accentColor}-5)`,\n ];\n}\n\nfunction dateToIso(date: Date): string {\n const y = date.getFullYear();\n const m = String(date.getMonth() + 1).padStart(2, \"0\");\n const d = String(date.getDate()).padStart(2, \"0\");\n return `${y}-${m}-${d}`;\n}\n\nfunction isoToLocal(iso: string): Date {\n const [y, m, d] = iso.split(\"-\").map(Number);\n return new Date(y, m - 1, d);\n}\n\nfunction fullDateLabel(iso: string, formatter: Intl.DateTimeFormat): string {\n return formatter.format(isoToLocal(iso));\n}\n\ninterface GridCell {\n iso: string;\n count: number;\n level: number;\n future: boolean;\n}\n\nexport function ContributionGraph({\n data,\n maxLevel = 4,\n cellSize = 12,\n cellGap = 3,\n responsive = true,\n minCellSize = 8,\n maxCellSize = 24,\n weekStartDay = 1,\n showMonthLabels = true,\n showDayLabels = true,\n showLegend = true,\n weeks = 52,\n ariaLabel = \"Contribution graph\",\n onDayClick,\n tooltipContent,\n className,\n}: ContributionGraphProps) {\n const monthFormatter = useDateTimeFormatter({ month: \"short\" });\n const weekdayFormatter = useDateTimeFormatter({ weekday: \"short\" });\n const fullDateFormatter = useDateTimeFormatter({\n weekday: \"long\",\n year: \"numeric\",\n month: \"long\",\n day: \"numeric\",\n });\n const { accentColor } = useContext(GnomeContext);\n const graphAccent = NAMED_ACCENTS.has(accentColor as GnomeNamedAccentColor)\n ? accentColor as GnomeNamedAccentColor\n : \"green\";\n const svgRef = useRef<SVGSVGElement>(null);\n const graphViewportRef = useRef<HTMLDivElement>(null);\n const tooltipRef = useRef<HTMLDivElement>(null);\n const colors = useMemo(() => accentScale(graphAccent), [graphAccent]);\n\n const [availableWidth, setAvailableWidth] = useState<number | null>(null);\n const [focusedCell, setFocusedCell] = useState({ col: 0, row: 0 });\n const [tooltip, setTooltip] = useState<{\n target: SVGRectElement;\n text: string;\n } | null>(null);\n const [tooltipPosition, setTooltipPosition] = useState<{\n left: number;\n top: number;\n } | null>(null);\n\n useEffect(() => {\n if (!responsive) return;\n\n const viewport = graphViewportRef.current;\n if (!viewport) return;\n\n const updateWidth = () => {\n const width = viewport.clientWidth || viewport.getBoundingClientRect().width;\n if (width > 0) setAvailableWidth(width);\n };\n\n updateWidth();\n\n if (typeof ResizeObserver === \"undefined\") {\n window.addEventListener(\"resize\", updateWidth);\n return () => window.removeEventListener(\"resize\", updateWidth);\n }\n\n const observer = new ResizeObserver(updateWidth);\n observer.observe(viewport);\n return () => observer.disconnect();\n }, [responsive]);\n\n useEffect(() => {\n if (!tooltip) return;\n\n const placeTooltip = () => {\n const tooltipNode = tooltipRef.current;\n if (!tooltipNode) return;\n\n const cellRect = tooltip.target.getBoundingClientRect();\n const tipRect = tooltipNode.getBoundingClientRect();\n const margin = 8;\n const gap = 6;\n const viewportWidth = document.documentElement.clientWidth || window.innerWidth;\n const viewportHeight = document.documentElement.clientHeight || window.innerHeight;\n const maxLeft = Math.max(margin, viewportWidth - tipRect.width - margin);\n const maxTop = Math.max(margin, viewportHeight - tipRect.height - margin);\n const centeredLeft = cellRect.left + cellRect.width / 2 - tipRect.width / 2;\n const aboveTop = cellRect.top - tipRect.height - gap;\n const preferredTop = aboveTop >= margin ? aboveTop : cellRect.bottom + gap;\n\n setTooltipPosition({\n left: Math.max(margin, Math.min(centeredLeft, maxLeft)),\n top: Math.max(margin, Math.min(preferredTop, maxTop)),\n });\n };\n\n placeTooltip();\n window.addEventListener(\"resize\", placeTooltip);\n window.addEventListener(\"scroll\", placeTooltip, { passive: true, capture: true });\n\n return () => {\n window.removeEventListener(\"resize\", placeTooltip);\n window.removeEventListener(\"scroll\", placeTooltip, { capture: true });\n };\n }, [tooltip]);\n\n const countMap = useMemo(() => {\n const map = new Map<string, number>();\n for (const d of data) map.set(d.date, d.count);\n return map;\n }, [data]);\n\n const maxCount = useMemo(\n () => Math.max(1, ...data.map((d) => d.count)),\n [data],\n );\n\n const dayLabelW = showDayLabels ? DAY_LABEL_WIDTH : 0;\n const monthLabelH = showMonthLabels ? MONTH_LABEL_HEIGHT : 0;\n const configuredWeeks = Math.max(1, Math.floor(weeks));\n const normalizedMinCellSize = Math.max(1, minCellSize);\n const normalizedMaxCellSize = Math.max(normalizedMinCellSize, maxCellSize);\n const fit = useMemo(() => {\n if (!responsive || availableWidth === null) {\n return { cellSize, weeks: configuredWeeks };\n }\n\n const gridWidth = Math.max(0, availableWidth - dayLabelW);\n const fittedCellSize = (gridWidth + cellGap) / configuredWeeks - cellGap;\n\n if (fittedCellSize >= normalizedMinCellSize) {\n return {\n cellSize: Math.min(normalizedMaxCellSize, fittedCellSize),\n weeks: configuredWeeks,\n };\n }\n\n const fittedWeeks = Math.floor((gridWidth + cellGap) / (normalizedMinCellSize + cellGap));\n return {\n cellSize: normalizedMinCellSize,\n weeks: Math.max(1, Math.min(configuredWeeks, fittedWeeks)),\n };\n }, [\n availableWidth,\n cellGap,\n cellSize,\n configuredWeeks,\n dayLabelW,\n normalizedMaxCellSize,\n normalizedMinCellSize,\n responsive,\n ]);\n const visibleWeeks = fit.weeks;\n const resolvedCellSize = fit.cellSize;\n const stride = resolvedCellSize + cellGap;\n\n const { grid, monthLabels } = useMemo(() => {\n const today = new Date();\n today.setHours(0, 0, 0, 0);\n\n // Align to start of current week\n const daysFromWeekStart = (today.getDay() - weekStartDay + 7) % 7;\n const lastWeekStart = new Date(today);\n lastWeekStart.setDate(today.getDate() - daysFromWeekStart);\n\n const firstDay = new Date(lastWeekStart);\n firstDay.setDate(lastWeekStart.getDate() - (visibleWeeks - 1) * 7);\n\n const gridResult: GridCell[][] = [];\n const labels: { col: number; month: string }[] = [];\n let lastMonth = -1;\n\n for (let col = 0; col < visibleWeeks; col++) {\n const colStart = new Date(firstDay);\n colStart.setDate(firstDay.getDate() + col * 7);\n\n // Month label when the column starts a new month\n if (colStart.getMonth() !== lastMonth) {\n labels.push({\n col,\n month: getShortMonth(colStart.getMonth(), monthFormatter),\n });\n lastMonth = colStart.getMonth();\n }\n\n const column: GridCell[] = [];\n for (let row = 0; row < 7; row++) {\n const date = new Date(firstDay);\n date.setDate(firstDay.getDate() + col * 7 + row);\n const future = date > today;\n const iso = dateToIso(date);\n const count = future ? 0 : (countMap.get(iso) ?? 0);\n const level =\n count === 0\n ? 0\n : Math.min(maxLevel, Math.ceil((count / maxCount) * maxLevel));\n column.push({ iso, count, level, future });\n }\n gridResult.push(column);\n }\n\n return { grid: gridResult, monthLabels: labels };\n }, [countMap, maxCount, maxLevel, visibleWeeks, weekStartDay, monthFormatter]);\n\n // Always label Mon(1), Wed(3), Fri(5) — rows shift with weekStartDay\n const labelRows = useMemo(\n () =>\n [1, 3, 5].map((dayIndex) => ({\n row: (dayIndex - weekStartDay + 7) % 7,\n label: getShortDay(dayIndex, weekdayFormatter),\n })),\n [weekStartDay, weekdayFormatter],\n );\n\n const svgWidth = dayLabelW + visibleWeeks * stride - cellGap;\n const svgHeight = monthLabelH + 7 * stride - cellGap;\n const cellRx = Math.min(4, Math.floor(resolvedCellSize / 3));\n\n useEffect(() => {\n setFocusedCell((current) => ({\n col: Math.min(visibleWeeks - 1, current.col),\n row: current.row,\n }));\n }, [visibleWeeks]);\n\n function focusCell(col: number, row: number) {\n const c = Math.max(0, Math.min(visibleWeeks - 1, col));\n const r = Math.max(0, Math.min(6, row));\n setFocusedCell({ col: c, row: r });\n svgRef.current\n ?.querySelector<SVGRectElement>(`[data-col=\"${c}\"][data-row=\"${r}\"]`)\n ?.focus();\n }\n\n function handleCellKey(e: KeyboardEvent, col: number, row: number) {\n const moves: Record<string, [number, number]> = {\n ArrowRight: [col + 1, row],\n ArrowLeft: [col - 1, row],\n ArrowDown: [col, row + 1],\n ArrowUp: [col, row - 1],\n };\n if (moves[e.key]) {\n e.preventDefault();\n focusCell(...moves[e.key]);\n } else if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n const cell = grid[col]?.[row];\n if (cell && !cell.future) onDayClick?.({ date: cell.iso, count: cell.count });\n }\n }\n\n function handleMouseEnter(\n e: React.MouseEvent<SVGRectElement>,\n text: string,\n ) {\n setTooltipPosition(null);\n setTooltip({\n target: e.currentTarget,\n text,\n });\n }\n\n function hideTooltip() {\n setTooltip(null);\n setTooltipPosition(null);\n }\n\n function cellColor(cell: GridCell): string {\n if (cell.future) return colors[0];\n return colors[Math.min(cell.level, colors.length - 1)] ?? colors[colors.length - 1];\n }\n\n function cellTooltip(cell: GridCell): string {\n if (cell.future) return fullDateLabel(cell.iso, fullDateFormatter);\n const formattedDate = fullDateLabel(cell.iso, fullDateFormatter);\n return (\n tooltipContent?.({ date: cell.iso, count: cell.count }) ??\n `${cell.count} contribution${cell.count !== 1 ? \"s\" : \"\"} on ${formattedDate}`\n );\n }\n\n return (\n <div\n className={[styles.wrapper, className].filter(Boolean).join(\" \")}\n >\n <div ref={graphViewportRef} className={styles.graphViewport}>\n <svg\n ref={svgRef}\n width={svgWidth}\n height={svgHeight}\n className={styles.svg}\n aria-label={ariaLabel}\n role=\"img\"\n data-cell-size={resolvedCellSize}\n data-visible-weeks={visibleWeeks}\n >\n {/* Month labels */}\n {showMonthLabels &&\n monthLabels.map(({ col, month }) => (\n <text\n key={`m-${col}`}\n x={dayLabelW + col * stride}\n y={12}\n className={styles.label}\n >\n {month}\n </text>\n ))}\n\n {/* Day-of-week labels: Mon, Wed, Fri */}\n {showDayLabels &&\n labelRows.map(({ row, label }) => (\n <text\n key={`d-${row}`}\n x={0}\n y={monthLabelH + row * stride + resolvedCellSize - 1}\n className={styles.label}\n >\n {label}\n </text>\n ))}\n\n {/* Activity grid */}\n <g role=\"grid\" aria-label={ariaLabel}>\n {grid.map((column, col) => (\n <g key={col} role=\"row\">\n {column.map((cell, row) => {\n const tipText = cellTooltip(cell);\n const isFocused =\n focusedCell.col === col && focusedCell.row === row;\n\n return (\n <rect\n key={`${col}-${row}`}\n data-col={col}\n data-row={row}\n x={dayLabelW + col * stride}\n y={monthLabelH + row * stride}\n width={resolvedCellSize}\n height={resolvedCellSize}\n rx={cellRx}\n fill={cellColor(cell)}\n opacity={cell.future ? 0.35 : 1}\n className={styles.cell}\n role=\"gridcell\"\n aria-label={tipText}\n aria-disabled={cell.future || undefined}\n tabIndex={isFocused ? 0 : -1}\n onClick={() => !cell.future && onDayClick?.({ date: cell.iso, count: cell.count })}\n onKeyDown={(e) => handleCellKey(e, col, row)}\n onFocus={() => setFocusedCell({ col, row })}\n onMouseEnter={(e) => handleMouseEnter(e, tipText)}\n onMouseLeave={hideTooltip}\n />\n );\n })}\n </g>\n ))}\n </g>\n </svg>\n </div>\n\n {tooltip && typeof document !== \"undefined\" && createPortal(\n <div\n ref={tooltipRef}\n className={styles.tooltip}\n style={\n tooltipPosition\n ? { left: tooltipPosition.left, top: tooltipPosition.top }\n : { visibility: \"hidden\", left: 0, top: 0 }\n }\n role=\"tooltip\"\n >\n {tooltip.text}\n </div>,\n document.body,\n )}\n\n {showLegend && (\n <div className={styles.legend}>\n <span className={styles.legendLabel}>Less</span>\n {Array.from({ length: maxLevel + 1 }, (_, i) => (\n <svg\n key={i}\n width={resolvedCellSize}\n height={resolvedCellSize}\n aria-hidden=\"true\"\n className={styles.legendCell}\n >\n <rect\n width={resolvedCellSize}\n height={resolvedCellSize}\n rx={cellRx}\n fill={colors[Math.min(i, colors.length - 1)]}\n />\n </svg>\n ))}\n <span className={styles.legendLabel}>More</span>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -12,15 +12,20 @@ export interface ContributionGraphProps {
|
|
|
12
12
|
* @default 4
|
|
13
13
|
*/
|
|
14
14
|
maxLevel?: number;
|
|
15
|
-
/**
|
|
16
|
-
* Colour scale — length must be maxLevel + 1 (index 0 = empty).
|
|
17
|
-
* Defaults to the Adwaita green palette.
|
|
18
|
-
*/
|
|
19
|
-
colorScale?: string[];
|
|
20
15
|
/** Cell side length in pixels. @default 12 */
|
|
21
16
|
cellSize?: number;
|
|
22
17
|
/** Gap between cells in pixels. @default 3 */
|
|
23
18
|
cellGap?: number;
|
|
19
|
+
/**
|
|
20
|
+
* Fit the graph to its container by resizing cells and reducing visible
|
|
21
|
+
* weeks when the configured range cannot stay legible.
|
|
22
|
+
* @default true
|
|
23
|
+
*/
|
|
24
|
+
responsive?: boolean;
|
|
25
|
+
/** Smallest responsive cell side length in pixels. @default 8 */
|
|
26
|
+
minCellSize?: number;
|
|
27
|
+
/** Largest responsive cell side length in pixels. @default 24 */
|
|
28
|
+
maxCellSize?: number;
|
|
24
29
|
/** 0 = Sunday · 1 = Monday (GNOME locale default). @default 1 */
|
|
25
30
|
weekStartDay?: 0 | 1;
|
|
26
31
|
/** @default true */
|
|
@@ -38,4 +43,4 @@ export interface ContributionGraphProps {
|
|
|
38
43
|
tooltipContent?: (day: ContributionDay) => string;
|
|
39
44
|
className?: string;
|
|
40
45
|
}
|
|
41
|
-
export declare function ContributionGraph({ data, maxLevel,
|
|
46
|
+
export declare function ContributionGraph({ data, maxLevel, cellSize, cellGap, responsive, minCellSize, maxCellSize, weekStartDay, showMonthLabels, showDayLabels, showLegend, weeks, ariaLabel, onDayClick, tooltipContent, className, }: ContributionGraphProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,23 +1,35 @@
|
|
|
1
1
|
import e from "./ContributionGraph.module.css.js";
|
|
2
|
-
import {
|
|
3
|
-
import { useMemo as
|
|
4
|
-
import {
|
|
2
|
+
import { GnomeContext as t, useDateTimeFormatter as n } from "../GnomeProvider/GnomeContext.js";
|
|
3
|
+
import { useContext as r, useEffect as i, useMemo as a, useRef as o, useState as s } from "react";
|
|
4
|
+
import { createPortal as c } from "react-dom";
|
|
5
|
+
import { jsx as l, jsxs as u } from "react/jsx-runtime";
|
|
5
6
|
//#region src/components/ContributionGraph/ContributionGraph.tsx
|
|
6
|
-
var
|
|
7
|
-
function
|
|
7
|
+
var d = new Date(2e3, 0, 2);
|
|
8
|
+
function f(e, t) {
|
|
8
9
|
return t.format(new Date(2e3, e));
|
|
9
10
|
}
|
|
10
|
-
function
|
|
11
|
-
let n = new Date(
|
|
12
|
-
return n.setDate(
|
|
11
|
+
function ee(e, t) {
|
|
12
|
+
let n = new Date(d);
|
|
13
|
+
return n.setDate(d.getDate() + e), t.format(n);
|
|
14
|
+
}
|
|
15
|
+
var te = 28, ne = 20, re = new Set([
|
|
16
|
+
"blue",
|
|
17
|
+
"green",
|
|
18
|
+
"yellow",
|
|
19
|
+
"orange",
|
|
20
|
+
"red",
|
|
21
|
+
"purple",
|
|
22
|
+
"brown"
|
|
23
|
+
]);
|
|
24
|
+
function ie(e) {
|
|
25
|
+
return [
|
|
26
|
+
"var(--gnome-card-shade-color)",
|
|
27
|
+
`var(--gnome-${e}-1)`,
|
|
28
|
+
`var(--gnome-${e}-2)`,
|
|
29
|
+
`var(--gnome-${e}-4)`,
|
|
30
|
+
`var(--gnome-${e}-5)`
|
|
31
|
+
];
|
|
13
32
|
}
|
|
14
|
-
var u = 28, d = 20, f = [
|
|
15
|
-
"var(--gnome-card-shade-color, rgba(0,0,0,0.07))",
|
|
16
|
-
"var(--gnome-green-1, #8ff0a4)",
|
|
17
|
-
"var(--gnome-green-2, #57e389)",
|
|
18
|
-
"var(--gnome-green-4, #2ec27e)",
|
|
19
|
-
"var(--gnome-green-5, #26a269)"
|
|
20
|
-
];
|
|
21
33
|
function p(e) {
|
|
22
34
|
return `${e.getFullYear()}-${String(e.getMonth() + 1).padStart(2, "0")}-${String(e.getDate()).padStart(2, "0")}`;
|
|
23
35
|
}
|
|
@@ -28,38 +40,92 @@ function m(e) {
|
|
|
28
40
|
function h(e, t) {
|
|
29
41
|
return t.format(m(e));
|
|
30
42
|
}
|
|
31
|
-
function g({ data:
|
|
32
|
-
let
|
|
43
|
+
function g({ data: d, maxLevel: m = 4, cellSize: g = 12, cellGap: _ = 3, responsive: v = !0, minCellSize: ae = 8, maxCellSize: oe = 24, weekStartDay: y = 1, showMonthLabels: b = !0, showDayLabels: x = !0, showLegend: se = !0, weeks: ce = 52, ariaLabel: S = "Contribution graph", onDayClick: C, tooltipContent: le, className: ue }) {
|
|
44
|
+
let w = n({ month: "short" }), T = n({ weekday: "short" }), E = n({
|
|
33
45
|
weekday: "long",
|
|
34
46
|
year: "numeric",
|
|
35
47
|
month: "long",
|
|
36
48
|
day: "numeric"
|
|
37
|
-
}),
|
|
49
|
+
}), { accentColor: D } = r(t), O = re.has(D) ? D : "green", k = o(null), A = o(null), j = o(null), M = a(() => ie(O), [O]), [N, de] = s(null), [P, F] = s({
|
|
38
50
|
col: 0,
|
|
39
51
|
row: 0
|
|
40
|
-
}), [
|
|
52
|
+
}), [I, L] = s(null), [R, z] = s(null);
|
|
53
|
+
i(() => {
|
|
54
|
+
if (!v) return;
|
|
55
|
+
let e = A.current;
|
|
56
|
+
if (!e) return;
|
|
57
|
+
let t = () => {
|
|
58
|
+
let t = e.clientWidth || e.getBoundingClientRect().width;
|
|
59
|
+
t > 0 && de(t);
|
|
60
|
+
};
|
|
61
|
+
if (t(), typeof ResizeObserver > "u") return window.addEventListener("resize", t), () => window.removeEventListener("resize", t);
|
|
62
|
+
let n = new ResizeObserver(t);
|
|
63
|
+
return n.observe(e), () => n.disconnect();
|
|
64
|
+
}, [v]), i(() => {
|
|
65
|
+
if (!I) return;
|
|
66
|
+
let e = () => {
|
|
67
|
+
let e = j.current;
|
|
68
|
+
if (!e) return;
|
|
69
|
+
let t = I.target.getBoundingClientRect(), n = e.getBoundingClientRect(), r = document.documentElement.clientWidth || window.innerWidth, i = document.documentElement.clientHeight || window.innerHeight, a = Math.max(8, r - n.width - 8), o = Math.max(8, i - n.height - 8), s = t.left + t.width / 2 - n.width / 2, c = t.top - n.height - 6, l = c >= 8 ? c : t.bottom + 6;
|
|
70
|
+
z({
|
|
71
|
+
left: Math.max(8, Math.min(s, a)),
|
|
72
|
+
top: Math.max(8, Math.min(l, o))
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
return e(), window.addEventListener("resize", e), window.addEventListener("scroll", e, {
|
|
76
|
+
passive: !0,
|
|
77
|
+
capture: !0
|
|
78
|
+
}), () => {
|
|
79
|
+
window.removeEventListener("resize", e), window.removeEventListener("scroll", e, { capture: !0 });
|
|
80
|
+
};
|
|
81
|
+
}, [I]);
|
|
82
|
+
let B = a(() => {
|
|
41
83
|
let e = /* @__PURE__ */ new Map();
|
|
42
|
-
for (let t of
|
|
84
|
+
for (let t of d) e.set(t.date, t.count);
|
|
43
85
|
return e;
|
|
44
|
-
}, [
|
|
86
|
+
}, [d]), V = a(() => Math.max(1, ...d.map((e) => e.count)), [d]), H = x ? te : 0, U = b ? ne : 0, W = Math.max(1, Math.floor(ce)), G = Math.max(1, ae), K = Math.max(G, oe), q = a(() => {
|
|
87
|
+
if (!v || N === null) return {
|
|
88
|
+
cellSize: g,
|
|
89
|
+
weeks: W
|
|
90
|
+
};
|
|
91
|
+
let e = Math.max(0, N - H), t = (e + _) / W - _;
|
|
92
|
+
if (t >= G) return {
|
|
93
|
+
cellSize: Math.min(K, t),
|
|
94
|
+
weeks: W
|
|
95
|
+
};
|
|
96
|
+
let n = Math.floor((e + _) / (G + _));
|
|
97
|
+
return {
|
|
98
|
+
cellSize: G,
|
|
99
|
+
weeks: Math.max(1, Math.min(W, n))
|
|
100
|
+
};
|
|
101
|
+
}, [
|
|
102
|
+
N,
|
|
103
|
+
_,
|
|
104
|
+
g,
|
|
105
|
+
W,
|
|
106
|
+
H,
|
|
107
|
+
K,
|
|
108
|
+
G,
|
|
109
|
+
v
|
|
110
|
+
]), J = q.weeks, Y = q.cellSize, X = Y + _, { grid: Z, monthLabels: fe } = a(() => {
|
|
45
111
|
let e = /* @__PURE__ */ new Date();
|
|
46
112
|
e.setHours(0, 0, 0, 0);
|
|
47
113
|
let t = (e.getDay() - y + 7) % 7, n = new Date(e);
|
|
48
114
|
n.setDate(e.getDate() - t);
|
|
49
115
|
let r = new Date(n);
|
|
50
|
-
r.setDate(n.getDate() - (
|
|
116
|
+
r.setDate(n.getDate() - (J - 1) * 7);
|
|
51
117
|
let i = [], a = [], o = -1;
|
|
52
|
-
for (let t = 0; t <
|
|
118
|
+
for (let t = 0; t < J; t++) {
|
|
53
119
|
let n = new Date(r);
|
|
54
120
|
n.setDate(r.getDate() + t * 7), n.getMonth() !== o && (a.push({
|
|
55
121
|
col: t,
|
|
56
|
-
month:
|
|
122
|
+
month: f(n.getMonth(), w)
|
|
57
123
|
}), o = n.getMonth());
|
|
58
124
|
let s = [];
|
|
59
125
|
for (let n = 0; n < 7; n++) {
|
|
60
126
|
let i = new Date(r);
|
|
61
127
|
i.setDate(r.getDate() + t * 7 + n);
|
|
62
|
-
let a = i > e, o = p(i), c = a ? 0 :
|
|
128
|
+
let a = i > e, o = p(i), c = a ? 0 : B.get(o) ?? 0, l = c === 0 ? 0 : Math.min(m, Math.ceil(c / V * m));
|
|
63
129
|
s.push({
|
|
64
130
|
iso: o,
|
|
65
131
|
count: c,
|
|
@@ -74,157 +140,172 @@ function g({ data: s, maxLevel: m = 4, colorScale: g, cellSize: _ = 12, cellGap:
|
|
|
74
140
|
monthLabels: a
|
|
75
141
|
};
|
|
76
142
|
}, [
|
|
77
|
-
z,
|
|
78
143
|
B,
|
|
144
|
+
V,
|
|
79
145
|
m,
|
|
80
|
-
|
|
146
|
+
J,
|
|
81
147
|
y,
|
|
82
|
-
|
|
83
|
-
]),
|
|
148
|
+
w
|
|
149
|
+
]), pe = a(() => [
|
|
84
150
|
1,
|
|
85
151
|
3,
|
|
86
152
|
5
|
|
87
153
|
].map((e) => ({
|
|
88
154
|
row: (e - y + 7) % 7,
|
|
89
|
-
label:
|
|
90
|
-
})), [y,
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
155
|
+
label: ee(e, T)
|
|
156
|
+
})), [y, T]), Q = H + J * X - _, me = U + 7 * X - _, $ = Math.min(4, Math.floor(Y / 3));
|
|
157
|
+
i(() => {
|
|
158
|
+
F((e) => ({
|
|
159
|
+
col: Math.min(J - 1, e.col),
|
|
160
|
+
row: e.row
|
|
161
|
+
}));
|
|
162
|
+
}, [J]);
|
|
163
|
+
function he(e, t) {
|
|
164
|
+
let n = Math.max(0, Math.min(J - 1, e)), r = Math.max(0, Math.min(6, t));
|
|
165
|
+
F({
|
|
94
166
|
col: n,
|
|
95
167
|
row: r
|
|
96
|
-
}),
|
|
168
|
+
}), k.current?.querySelector(`[data-col="${n}"][data-row="${r}"]`)?.focus();
|
|
97
169
|
}
|
|
98
|
-
function
|
|
170
|
+
function ge(e, t, n) {
|
|
99
171
|
let r = {
|
|
100
172
|
ArrowRight: [t + 1, n],
|
|
101
173
|
ArrowLeft: [t - 1, n],
|
|
102
174
|
ArrowDown: [t, n + 1],
|
|
103
175
|
ArrowUp: [t, n - 1]
|
|
104
176
|
};
|
|
105
|
-
if (r[e.key]) e.preventDefault(),
|
|
177
|
+
if (r[e.key]) e.preventDefault(), he(...r[e.key]);
|
|
106
178
|
else if (e.key === "Enter" || e.key === " ") {
|
|
107
179
|
e.preventDefault();
|
|
108
|
-
let r =
|
|
109
|
-
r && !r.future &&
|
|
180
|
+
let r = Z[t]?.[n];
|
|
181
|
+
r && !r.future && C?.({
|
|
110
182
|
date: r.iso,
|
|
111
183
|
count: r.count
|
|
112
184
|
});
|
|
113
185
|
}
|
|
114
186
|
}
|
|
115
|
-
function
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
let r = n.getBoundingClientRect(), i = e.currentTarget.getBoundingClientRect();
|
|
119
|
-
R({
|
|
120
|
-
x: i.left - r.left + _ / 2,
|
|
121
|
-
y: i.top - r.top,
|
|
187
|
+
function _e(e, t) {
|
|
188
|
+
z(null), L({
|
|
189
|
+
target: e.currentTarget,
|
|
122
190
|
text: t
|
|
123
191
|
});
|
|
124
192
|
}
|
|
125
|
-
function
|
|
126
|
-
|
|
193
|
+
function ve() {
|
|
194
|
+
L(null), z(null);
|
|
127
195
|
}
|
|
128
|
-
function
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
196
|
+
function ye(e) {
|
|
197
|
+
return e.future ? M[0] : M[Math.min(e.level, M.length - 1)] ?? M[M.length - 1];
|
|
198
|
+
}
|
|
199
|
+
function be(e) {
|
|
200
|
+
if (e.future) return h(e.iso, E);
|
|
201
|
+
let t = h(e.iso, E);
|
|
202
|
+
return le?.({
|
|
132
203
|
date: e.iso,
|
|
133
204
|
count: e.count
|
|
134
205
|
}) ?? `${e.count} contribution${e.count === 1 ? "" : "s"} on ${t}`;
|
|
135
206
|
}
|
|
136
|
-
return /* @__PURE__ */
|
|
137
|
-
|
|
138
|
-
className: [e.wrapper, D].filter(Boolean).join(" "),
|
|
207
|
+
return /* @__PURE__ */ u("div", {
|
|
208
|
+
className: [e.wrapper, ue].filter(Boolean).join(" "),
|
|
139
209
|
children: [
|
|
140
|
-
/* @__PURE__ */
|
|
141
|
-
ref:
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
210
|
+
/* @__PURE__ */ l("div", {
|
|
211
|
+
ref: A,
|
|
212
|
+
className: e.graphViewport,
|
|
213
|
+
children: /* @__PURE__ */ u("svg", {
|
|
214
|
+
ref: k,
|
|
215
|
+
width: Q,
|
|
216
|
+
height: me,
|
|
217
|
+
className: e.svg,
|
|
218
|
+
"aria-label": S,
|
|
219
|
+
role: "img",
|
|
220
|
+
"data-cell-size": Y,
|
|
221
|
+
"data-visible-weeks": J,
|
|
222
|
+
children: [
|
|
223
|
+
b && fe.map(({ col: t, month: n }) => /* @__PURE__ */ l("text", {
|
|
224
|
+
x: H + t * X,
|
|
225
|
+
y: 12,
|
|
226
|
+
className: e.label,
|
|
227
|
+
children: n
|
|
228
|
+
}, `m-${t}`)),
|
|
229
|
+
x && pe.map(({ row: t, label: n }) => /* @__PURE__ */ l("text", {
|
|
230
|
+
x: 0,
|
|
231
|
+
y: U + t * X + Y - 1,
|
|
232
|
+
className: e.label,
|
|
233
|
+
children: n
|
|
234
|
+
}, `d-${t}`)),
|
|
235
|
+
/* @__PURE__ */ l("g", {
|
|
236
|
+
role: "grid",
|
|
237
|
+
"aria-label": S,
|
|
238
|
+
children: Z.map((t, n) => /* @__PURE__ */ l("g", {
|
|
239
|
+
role: "row",
|
|
240
|
+
children: t.map((t, r) => {
|
|
241
|
+
let i = be(t), a = P.col === n && P.row === r;
|
|
242
|
+
return /* @__PURE__ */ l("rect", {
|
|
243
|
+
"data-col": n,
|
|
244
|
+
"data-row": r,
|
|
245
|
+
x: H + n * X,
|
|
246
|
+
y: U + r * X,
|
|
247
|
+
width: Y,
|
|
248
|
+
height: Y,
|
|
249
|
+
rx: $,
|
|
250
|
+
fill: ye(t),
|
|
251
|
+
opacity: t.future ? .35 : 1,
|
|
252
|
+
className: e.cell,
|
|
253
|
+
role: "gridcell",
|
|
254
|
+
"aria-label": i,
|
|
255
|
+
"aria-disabled": t.future || void 0,
|
|
256
|
+
tabIndex: a ? 0 : -1,
|
|
257
|
+
onClick: () => !t.future && C?.({
|
|
258
|
+
date: t.iso,
|
|
259
|
+
count: t.count
|
|
260
|
+
}),
|
|
261
|
+
onKeyDown: (e) => ge(e, n, r),
|
|
262
|
+
onFocus: () => F({
|
|
263
|
+
col: n,
|
|
264
|
+
row: r
|
|
265
|
+
}),
|
|
266
|
+
onMouseEnter: (e) => _e(e, i),
|
|
267
|
+
onMouseLeave: ve
|
|
268
|
+
}, `${n}-${r}`);
|
|
269
|
+
})
|
|
270
|
+
}, n))
|
|
271
|
+
})
|
|
272
|
+
]
|
|
273
|
+
})
|
|
198
274
|
}),
|
|
199
|
-
|
|
275
|
+
I && typeof document < "u" && c(/* @__PURE__ */ l("div", {
|
|
276
|
+
ref: j,
|
|
200
277
|
className: e.tooltip,
|
|
201
|
-
style: {
|
|
202
|
-
left:
|
|
203
|
-
top:
|
|
278
|
+
style: R ? {
|
|
279
|
+
left: R.left,
|
|
280
|
+
top: R.top
|
|
281
|
+
} : {
|
|
282
|
+
visibility: "hidden",
|
|
283
|
+
left: 0,
|
|
284
|
+
top: 0
|
|
204
285
|
},
|
|
205
286
|
role: "tooltip",
|
|
206
|
-
children:
|
|
207
|
-
}),
|
|
208
|
-
|
|
287
|
+
children: I.text
|
|
288
|
+
}), document.body),
|
|
289
|
+
se && /* @__PURE__ */ u("div", {
|
|
209
290
|
className: e.legend,
|
|
210
291
|
children: [
|
|
211
|
-
/* @__PURE__ */
|
|
292
|
+
/* @__PURE__ */ l("span", {
|
|
212
293
|
className: e.legendLabel,
|
|
213
294
|
children: "Less"
|
|
214
295
|
}),
|
|
215
|
-
Array.from({ length: m + 1 }, (t, n) => /* @__PURE__ */
|
|
216
|
-
width:
|
|
217
|
-
height:
|
|
296
|
+
Array.from({ length: m + 1 }, (t, n) => /* @__PURE__ */ l("svg", {
|
|
297
|
+
width: Y,
|
|
298
|
+
height: Y,
|
|
218
299
|
"aria-hidden": "true",
|
|
219
300
|
className: e.legendCell,
|
|
220
|
-
children: /* @__PURE__ */
|
|
221
|
-
width:
|
|
222
|
-
height:
|
|
223
|
-
rx:
|
|
224
|
-
fill:
|
|
301
|
+
children: /* @__PURE__ */ l("rect", {
|
|
302
|
+
width: Y,
|
|
303
|
+
height: Y,
|
|
304
|
+
rx: $,
|
|
305
|
+
fill: M[Math.min(n, M.length - 1)]
|
|
225
306
|
})
|
|
226
307
|
}, n)),
|
|
227
|
-
/* @__PURE__ */
|
|
308
|
+
/* @__PURE__ */ l("span", {
|
|
228
309
|
className: e.legendLabel,
|
|
229
310
|
children: "More"
|
|
230
311
|
})
|