This commit is contained in:
Amruth Pillai
2026-01-20 20:18:28 +01:00
parent 3cbf953ad2
commit 5688c97ee0
72 changed files with 1706 additions and 1567 deletions
+1 -1
View File
@@ -113,7 +113,7 @@ function Header() {
{basics.customFields.map((field) => (
<div key={field.id} className="basics-item-custom">
<PageIcon icon={field.icon} />
<span>{field.text}</span>
{field.link ? <PageLink url={field.link} label={field.text} /> : <span>{field.text}</span>}
</div>
))}
</div>
+1 -1
View File
@@ -92,7 +92,7 @@ function Header() {
{basics.customFields.map((field) => (
<div key={field.id} className="basics-item-custom">
<PageIcon icon={field.icon} />
<span>{field.text}</span>
{field.link ? <PageLink url={field.link} label={field.text} /> : <span>{field.text}</span>}
</div>
))}
</div>
@@ -104,7 +104,7 @@ function Header() {
{basics.customFields.map((field) => (
<div key={field.id} className="basics-item-custom">
<PageIcon icon={field.icon} />
<span>{field.text}</span>
{field.link ? <PageLink url={field.link} label={field.text} /> : <span>{field.text}</span>}
</div>
))}
</div>
+1 -1
View File
@@ -101,7 +101,7 @@ function Header() {
{basics.customFields.map((field) => (
<div key={field.id} className="basics-item-custom">
<PageIcon icon={field.icon} />
<span>{field.text}</span>
{field.link ? <PageLink url={field.link} label={field.text} /> : <span>{field.text}</span>}
</div>
))}
</div>
+1 -1
View File
@@ -113,7 +113,7 @@ function Header() {
{basics.customFields.map((field) => (
<div key={field.id} className="basics-item-custom">
<PageIcon icon={field.icon} />
<span>{field.text}</span>
{field.link ? <PageLink url={field.link} label={field.text} /> : <span>{field.text}</span>}
</div>
))}
</div>
+1 -1
View File
@@ -103,7 +103,7 @@ function Header() {
{basics.customFields.map((field) => (
<div key={field.id} className="basics-item-custom">
<PageIcon icon={field.icon} />
<span>{field.text}</span>
{field.link ? <PageLink url={field.link} label={field.text} /> : <span>{field.text}</span>}
</div>
))}
</div>
+1 -1
View File
@@ -87,7 +87,7 @@ function Header() {
{basics.customFields.map((field) => (
<div key={field.id} className="basics-item-custom">
<PageIcon icon={field.icon} />
<span>{field.text}</span>
{field.link ? <PageLink url={field.link} label={field.text} /> : <span>{field.text}</span>}
</div>
))}
</div>
+1 -1
View File
@@ -106,7 +106,7 @@ function Header() {
{basics.customFields.map((field) => (
<div key={field.id} className="basics-item-custom">
<PageIcon icon={field.icon} />
<span>{field.text}</span>
{field.link ? <PageLink url={field.link} label={field.text} /> : <span>{field.text}</span>}
</div>
))}
</div>
+1 -1
View File
@@ -100,7 +100,7 @@ function Header() {
{basics.customFields.map((field) => (
<div key={field.id} className="basics-item-custom">
<PageIcon icon={field.icon} />
<span>{field.text}</span>
{field.link ? <PageLink url={field.link} label={field.text} /> : <span>{field.text}</span>}
</div>
))}
</div>
+1 -1
View File
@@ -84,7 +84,7 @@ function Header() {
{basics.customFields.map((field) => (
<div key={field.id} className="basics-item-custom">
<PageIcon icon={field.icon} />
<span>{field.text}</span>
{field.link ? <PageLink url={field.link} label={field.text} /> : <span>{field.text}</span>}
</div>
))}
</div>
+1 -1
View File
@@ -102,7 +102,7 @@ function Header() {
{basics.customFields.map((field) => (
<div key={field.id} className="basics-item-custom">
<PageIcon icon={field.icon} />
<span>{field.text}</span>
{field.link ? <PageLink url={field.link} label={field.text} /> : <span>{field.text}</span>}
</div>
))}
</div>
+1 -1
View File
@@ -85,7 +85,7 @@ function Header() {
{basics.customFields.map((field) => (
<div key={field.id} className="basics-item-custom">
<PageIcon icon={field.icon} />
<span>{field.text}</span>
{field.link ? <PageLink url={field.link} label={field.text} /> : <span>{field.text}</span>}
</div>
))}
</div>
@@ -422,6 +422,7 @@ export class ReactiveResumeV4JSONImporter {
id: field.id ?? generateId(),
icon: field.icon ?? "",
text: field.text ?? "",
link: "",
})),
},
summary: {
+1
View File
@@ -27,6 +27,7 @@ export function Hero() {
muted
autoPlay
playsInline
// @ts-expect-error - typescript doesn't know about fetchpriority for video elements
fetchpriority="high"
src="/videos/timelapse.webm"
aria-label={t`Timelapse demonstration of building a resume with Reactive Resume`}
@@ -1,12 +1,13 @@
import { Trans } from "@lingui/react/macro";
import { DotsSixVerticalIcon, ListPlusIcon, XIcon } from "@phosphor-icons/react";
import { DotsSixVerticalIcon, LinkIcon, ListPlusIcon, XIcon } from "@phosphor-icons/react";
import { Reorder, useDragControls } from "motion/react";
import { useFieldArray, useFormContext, useWatch } from "react-hook-form";
import { Controller, useFieldArray, useFormContext, useWatch } from "react-hook-form";
import type z from "zod";
import { Button } from "@/components/animate-ui/components/buttons/button";
import { IconPicker } from "@/components/input/icon-picker";
import { FormControl, FormField, FormItem } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import type { basicsSchema } from "@/schema/resume/data";
import { generateId } from "@/utils/string";
@@ -41,7 +42,7 @@ export function CustomFieldsSection({ onSubmit }: Props) {
}
function handleAdd() {
customFieldsArray.append({ id: generateId(), icon: "acorn", text: "" });
customFieldsArray.append({ id: generateId(), icon: "acorn", text: "", link: "" });
form.handleSubmit(onSubmit)();
}
@@ -87,7 +88,39 @@ export function CustomFieldsSection({ onSubmit }: Props) {
)}
/>
<Button size="icon" variant="ghost" className="ml-2" onClick={() => handleRemove(index)}>
<Popover>
<PopoverTrigger asChild>
<Button size="icon" variant="ghost" className="ml-1">
<LinkIcon />
</Button>
</PopoverTrigger>
<PopoverContent align="center">
<div className="flex flex-col gap-y-1.5">
<span className="text-muted-foreground text-xs">
<Trans>Enter the URL to link to</Trans>
</span>
<Controller
control={form.control}
name={`customFields.${index}.link`}
render={({ field }) => (
<Input
type="url"
value={field.value}
placeholder="Must start with https://"
onChange={(e) => {
field.onChange(e.target.value);
form.handleSubmit(onSubmit)();
}}
/>
)}
/>
</div>
</PopoverContent>
</Popover>
<Button size="icon" variant="ghost" onClick={() => handleRemove(index)}>
<XIcon />
</Button>
</CustomFieldItem>
+11 -60
View File
@@ -7,65 +7,6 @@ export const iconSchema = z
"The icon to display for the custom field. Must be a valid icon name from @phosphor-icons/web icon set, or an empty string to hide. Default to '' (empty string) when unsure which icons are available.",
);
export const localeSchema = z
.union([
z.literal("af-ZA"),
z.literal("am-ET"),
z.literal("ar-SA"),
z.literal("az-AZ"),
z.literal("bg-BG"),
z.literal("bn-BD"),
z.literal("ca-ES"),
z.literal("cs-CZ"),
z.literal("da-DK"),
z.literal("de-DE"),
z.literal("el-GR"),
z.literal("en-US"),
z.literal("es-ES"),
z.literal("fa-IR"),
z.literal("fi-FI"),
z.literal("fr-FR"),
z.literal("he-IL"),
z.literal("hi-IN"),
z.literal("hu-HU"),
z.literal("id-ID"),
z.literal("it-IT"),
z.literal("ja-JP"),
z.literal("km-KH"),
z.literal("kn-IN"),
z.literal("ko-KR"),
z.literal("lt-LT"),
z.literal("lv-LV"),
z.literal("ml-IN"),
z.literal("mr-IN"),
z.literal("ms-MY"),
z.literal("ne-NP"),
z.literal("nl-NL"),
z.literal("no-NO"),
z.literal("or-IN"),
z.literal("pl-PL"),
z.literal("pt-BR"),
z.literal("pt-PT"),
z.literal("ro-RO"),
z.literal("ru-RU"),
z.literal("sk-SK"),
z.literal("sq-AL"),
z.literal("sr-SP"),
z.literal("sv-SE"),
z.literal("ta-IN"),
z.literal("te-IN"),
z.literal("th-TH"),
z.literal("tr-TR"),
z.literal("uk-UA"),
z.literal("uz-UZ"),
z.literal("vi-VN"),
z.literal("zh-CN"),
z.literal("zh-TW"),
z.literal("zu-ZA"),
])
.describe("The language used in the resume, used for displaying pre-translated section headings, if not overridden.")
.catch("en-US");
export const urlSchema = z.object({
url: z
.string()
@@ -124,6 +65,11 @@ export const customFieldSchema = z.object({
id: z.string().describe("The unique identifier for the custom field. Usually generated as a UUID."),
icon: iconSchema,
text: z.string().describe("The text to display for the custom field."),
link: z
.url()
.or(z.literal(""))
.describe("If the custom field should be a link, the URL to link to. Leave blank to hide.")
.catch(""),
});
export const basicsSchema = z.object({
@@ -431,7 +377,10 @@ export const pageSchema = z.object({
marginX: z.number().min(0).describe("The horizontal margin of the page, defined in points (pt)."),
marginY: z.number().min(0).describe("The vertical margin of the page, defined in points (pt)."),
format: z.enum(["a4", "letter"]).describe("The format of the page. Can be 'a4' or 'letter'."),
locale: localeSchema,
locale: z
.string()
.describe("The locale of the page. Used for displaying pre-translated section headings, if not overridden.")
.catch("en-US"),
hideIcons: z.boolean().describe("Whether to hide the icons of the sections.").catch(false),
});
@@ -683,11 +632,13 @@ export const sampleResumeData: ResumeData = {
id: "cf1",
icon: "github-logo",
text: "github.com/dkowalski-dev",
link: "https://github.com/dkowalski-dev",
},
{
id: "cf2",
icon: "game-controller",
text: "itch.io/dkowalski",
link: "https://itch.io/dkowalski",
},
],
},
-8
View File
@@ -1,19 +1,11 @@
/// <reference types="vite-plugin-pwa/client" />
import type { AriaAttributes, DOMAttributes } from "react";
declare const __APP_VERSION__: string;
declare module "*.css";
declare module "@fontsource/*" {}
declare module "@fontsource-variable/*" {}
declare module "react" {
interface HTMLAttributes<T> extends AriaAttributes, DOMAttributes<T> {
fetchpriority?: "high" | "low" | "auto";
}
}
declare namespace NodeJS {
interface ProcessEnv {
// Basics