Files
Reactive-Resume/src/components/input/url-input.tsx
T
Amruth PillaiandCursor 8d347f5162 Feature: Implement Embedding Links in Titles of all Section Items (#2662)
* feat: add options.showLinkInTitle to baseItemSchema

Add itemOptionsSchema with showLinkInTitle boolean property to control
whether the website URL is rendered as a hyperlink on the title instead
of a separate link at the bottom. The field is optional for backwards
compatibility with existing resumes.

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat: add hideLabelButton prop to URLInput

When hideLabelButton is true, the tag/label button is hidden from the
URL input. This is used when showLinkInTitle is enabled since the label
is not needed when the link is shown in the title.

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat: create LinkedTitle component for title-as-link rendering

Add a reusable component that conditionally renders the title as a
hyperlink when showLinkInTitle is true and a website URL is provided.

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat: add showLinkInTitle option to experience section

- Add Switch toggle in experience dialog for showLinkInTitle option
- Update URLInput to hide label button when showLinkInTitle is enabled
- Use LinkedTitle component in experience-item for conditional link rendering
- Hide bottom website link when showLinkInTitle is enabled

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat: add showLinkInTitle option to all section items

- Update education, projects, awards, certifications, publications,
  volunteer, references, and profiles dialogs with Switch toggle
- Add LinkedTitle component usage in all corresponding item components
- Conditionally hide bottom website link when showLinkInTitle is enabled
- Add hideLabelButton prop to URLInput when showLinkInTitle is enabled

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore: extract i18n strings for showLinkInTitle feature

Add "Show link in title" translation string to all locale catalogs.

Co-authored-by: Cursor <cursoragent@cursor.com>

* update dependencies, fix an issue with glalie template and non-clickable links, fix better-auth type error

* remove unused export

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-01-31 12:16:23 +01:00

86 lines
2.4 KiB
TypeScript

import { t } from "@lingui/core/macro";
import { Trans } from "@lingui/react/macro";
import { TagIcon } from "@phosphor-icons/react";
import { useCallback, useMemo } from "react";
import type z from "zod";
import type { urlSchema } from "@/schema/resume/data";
import { cn } from "@/utils/style";
import { Input } from "../ui/input";
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText } from "../ui/input-group";
import { Label } from "../ui/label";
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
const PREFIX = "https://";
function stripPrefix(url: string) {
return url.startsWith(PREFIX) ? url.slice(PREFIX.length) : url;
}
function ensurePrefix(url: string) {
if (url === "") return "";
return url.startsWith(PREFIX) ? url : PREFIX + url;
}
type Props = Omit<React.ComponentProps<"input">, "value" | "onChange"> & {
value: z.infer<typeof urlSchema>;
onChange: (value: z.infer<typeof urlSchema>) => void;
hideLabelButton?: boolean;
};
export function URLInput({ value, onChange, hideLabelButton, ...props }: Props) {
const handleUrlChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
onChange({
url: ensurePrefix(e.target.value),
label: value.label,
});
},
[onChange, value.label],
);
const handleLabelChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
onChange({ url: value.url, label: e.target.value });
},
[onChange, value.url],
);
const urlValue = useMemo(() => stripPrefix(value.url), [value.url]);
return (
<InputGroup>
<InputGroupAddon align="inline-start">
<InputGroupText>{PREFIX}</InputGroupText>
</InputGroupAddon>
<InputGroupInput
value={urlValue}
className={cn(props.className, "ps-0!")}
onChange={handleUrlChange}
{...props}
/>
{!hideLabelButton && (
<InputGroupAddon align="inline-end">
<Popover>
<PopoverTrigger asChild>
<InputGroupButton size="icon-sm" title={t`Add a label to the URL`}>
<TagIcon />
</InputGroupButton>
</PopoverTrigger>
<PopoverContent className="pt-3">
<div className="grid gap-2" onClick={(e) => e.stopPropagation()}>
<Label htmlFor="url-label">
<Trans>Label</Trans>
</Label>
<Input id="url-label" name="url-label" value={value.label} onChange={handleLabelChange} />
</div>
</PopoverContent>
</Popover>
</InputGroupAddon>
)}
</InputGroup>
);
}