Merge branch 'main' of github.com:amruthpillai/reactive-resume

This commit is contained in:
Amruth Pillai
2026-06-01 10:31:53 +02:00
19 changed files with 381 additions and 77 deletions
+1
View File
@@ -0,0 +1 @@
export const appVersion = typeof __APP_VERSION__ === "undefined" ? "0.0.0" : __APP_VERSION__;
+2 -1
View File
@@ -4,6 +4,7 @@ import { onError } from "@orpc/client";
import { createRouterClient } from "@orpc/server";
import router from "@reactive-resume/api/routers";
import { MCP_TOOL_NAME, registerPrompts, registerResources, registerTools } from "@reactive-resume/mcp";
import { appVersion } from "../app-version";
import { getRequestLocale } from "../rpc/locale";
function createRequestClient(request: Request): RouterClient<typeof router> {
@@ -25,7 +26,7 @@ export async function createMcpServer(request: Request) {
const server = new McpServer(
{
name: "reactive-resume",
version: __APP_VERSION__,
version: appVersion,
title: "Reactive Resume",
websiteUrl: "https://rxresu.me",
description:
+2 -1
View File
@@ -8,6 +8,7 @@ import { downloadResumePdfProcedure } from "@reactive-resume/api/features/resume
import router from "@reactive-resume/api/routers";
import { env } from "@reactive-resume/env/server";
import { resumeDataSchema } from "@reactive-resume/schema/resume/data";
import { appVersion } from "../app-version";
import { mergeResponseHeaders } from "../http/headers";
import { getRequestLocale } from "../rpc/locale";
@@ -44,7 +45,7 @@ export async function handleOpenApi(request: Request) {
const spec = await openAPIGenerator.generate(openAPIRouter, {
info: {
title: "Reactive Resume",
version: __APP_VERSION__,
version: appVersion,
description: "Reactive Resume API",
license: { name: "MIT", url: "https://github.com/amruthpillai/reactive-resume/blob/main/LICENSE" },
contact: { name: "Amruth Pillai", email: "hello@amruthpillai.com", url: "https://amruthpillai.com" },
+2 -1
View File
@@ -2,6 +2,7 @@ import { oauthProviderAuthServerMetadata, oauthProviderOpenIdConfigMetadata } fr
import { auth } from "@reactive-resume/auth/config";
import { env } from "@reactive-resume/env/server";
import { buildMcpServerCard } from "@reactive-resume/mcp/server-card";
import { appVersion } from "../app-version";
const oauthAuthorizationServerHandler = oauthProviderAuthServerMetadata(auth);
const openIdConfigurationHandler = oauthProviderOpenIdConfigMetadata(auth);
@@ -11,7 +12,7 @@ export function handleWellKnownFallback() {
}
export function handleMcpServerCard() {
return Response.json(buildMcpServerCard(__APP_VERSION__), {
return Response.json(buildMcpServerCard(appVersion), {
headers: {
"Content-Type": "application/json",
"Cache-Control": "public, max-age=60, stale-while-revalidate=120",
+2 -1
View File
@@ -1,5 +1,6 @@
import z from "zod";
import { resumeDataSchema } from "@reactive-resume/schema/resume/data";
import { appVersion } from "../app-version";
export function handleSchemaJson() {
const resumeDataJSONSchema = z.toJSONSchema(resumeDataSchema);
@@ -12,7 +13,7 @@ export function handleSchemaJson() {
"Surrogate-Control": "max-age=86400",
"X-Content-Type-Options": "nosniff",
"X-Robots-Tag": "index, follow",
ETag: __APP_VERSION__,
ETag: appVersion,
Vary: "Accept",
},
});
@@ -71,6 +71,21 @@ describe("LevelDisplay", () => {
expect(wrapper.getAttribute("aria-label")).toContain("4");
});
it("applies custom decoration and icon sizes in pixels", () => {
const { container } = render(
<LevelDisplay type="circle" icon="star" level={2} decorationSizePx={18} iconSizePx={20} />,
);
const shapes = container.querySelectorAll("div[data-active]");
expect(shapes[0]).toHaveStyle({ width: "18px", height: "18px" });
const { container: iconContainer } = render(
<LevelDisplay type="icon" icon="star" level={2} decorationSizePx={16} />,
);
const icon = iconContainer.querySelector("i");
expect(icon).toHaveStyle({ fontSize: "16px", width: "16px", height: "16px" });
});
it("merges extra className into the wrapper", () => {
const { container } = render(<LevelDisplay type="circle" icon="star" level={1} className="extra" />);
const wrapper = container.firstChild as HTMLElement;
+36 -5
View File
@@ -3,14 +3,35 @@ import type z from "zod";
import { t } from "@lingui/core/macro";
import { cn } from "@reactive-resume/utils/style";
type Props = z.infer<typeof levelDesignSchema> & React.ComponentProps<"div"> & { level: number };
type Props = z.infer<typeof levelDesignSchema> &
React.ComponentProps<"div"> & {
level: number;
decorationSizePx?: number | undefined;
iconSizePx?: number | undefined;
};
const LEVEL_ITEM_KEYS = ["level-1", "level-2", "level-3", "level-4", "level-5"] as const;
export function LevelDisplay({ icon, type, level, className, ...props }: Props) {
const defaultDecorationClassName = "size-2.5";
export function LevelDisplay({ icon, type, level, className, decorationSizePx, iconSizePx, ...props }: Props) {
if (level === 0) return null;
if (type === "hidden" || icon === "") return null;
const decorationStyle =
decorationSizePx === undefined
? undefined
: ({ width: decorationSizePx, height: decorationSizePx } satisfies React.CSSProperties);
const resolvedIconSizePx = iconSizePx ?? decorationSizePx;
const iconStyle =
resolvedIconSizePx === undefined
? undefined
: ({
fontSize: resolvedIconSizePx,
width: resolvedIconSizePx,
height: resolvedIconSizePx,
} satisfies React.CSSProperties);
return (
<div
role="img"
@@ -29,8 +50,10 @@ export function LevelDisplay({ icon, type, level, className, ...props }: Props)
<div
key={itemKey}
data-active={isActive}
style={decorationStyle}
className={cn(
"h-2.5 flex-1 border border-(--page-primary-color) border-x-0 first:border-l last:border-r",
"flex-1 border border-(--page-primary-color) border-x-0 first:border-l last:border-r",
decorationSizePx === undefined && "h-2.5",
isActive && "bg-(--page-primary-color)",
)}
/>
@@ -41,7 +64,13 @@ export function LevelDisplay({ icon, type, level, className, ...props }: Props)
return (
<i
key={itemKey}
className={cn("ph size-2.5 text-(--page-primary-color)", `ph-${icon}`, !isActive && "opacity-40")}
style={iconStyle}
className={cn(
"ph text-(--page-primary-color)",
resolvedIconSizePx === undefined && defaultDecorationClassName,
`ph-${icon}`,
!isActive && "opacity-40",
)}
/>
);
}
@@ -50,8 +79,10 @@ export function LevelDisplay({ icon, type, level, className, ...props }: Props)
<div
key={itemKey}
data-active={isActive}
style={decorationStyle}
className={cn(
"size-2.5 border border-(--page-primary-color)",
"border border-(--page-primary-color)",
decorationSizePx === undefined && defaultDecorationClassName,
isActive && "bg-(--page-primary-color)",
type === "circle" && "rounded-full",
type === "rectangle" && "w-7",
@@ -3,6 +3,8 @@ import { Trans } from "@lingui/react/macro";
import { useStore } from "@tanstack/react-form";
import { AnimatePresence, m } from "motion/react";
import { colorDesignSchema, levelDesignSchema } from "@reactive-resume/schema/resume/data";
import { resolveLevelDisplaySizes } from "@reactive-resume/schema/resume/level-display-sizes";
import { resolveStyleRuleFontSize } from "@reactive-resume/schema/resume/style-rules";
import { FormControl, FormItem, FormLabel, FormMessage } from "@reactive-resume/ui/components/form";
import { Input } from "@reactive-resume/ui/components/input";
import { Separator } from "@reactive-resume/ui/components/separator";
@@ -276,6 +278,13 @@ function LevelSectionForm() {
const previewType = useStore(form.store, (s) => s.values.type);
const previewIcon = useStore(form.store, (s) => s.values.icon);
const iconFontSize = resolveStyleRuleFontSize(resume.data, { slot: "icon" });
const levelFontSize = resolveStyleRuleFontSize(resume.data, { slot: "level" });
const { decorationSize, levelIconExplicitSize } = resolveLevelDisplaySizes({
bodyFontSize: resume.data.metadata.typography.body.fontSize,
iconFontSize,
levelFontSize,
});
return (
<form
@@ -294,7 +303,14 @@ function LevelSectionForm() {
style={{ "--page-primary-color": colors.primary, backgroundColor: colors.background } as React.CSSProperties}
className="flex items-center justify-center rounded-md p-6"
>
<LevelDisplay level={3} type={previewType} icon={previewIcon} className="w-full max-w-[220px] justify-center" />
<LevelDisplay
level={3}
type={previewType}
icon={previewIcon}
decorationSizePx={decorationSize}
iconSizePx={levelIconExplicitSize}
className="w-full max-w-[220px] justify-center"
/>
</div>
<div className="flex items-center gap-3">