- fixes #2562, add better error messages for duplicate resume slugs

- improvements made to ditgar template
- general improvements to all templates with backgrounds
- update dependencies and translations
- improved print function that handles single page and multi page resumes
This commit is contained in:
Amruth Pillai
2026-01-23 23:31:24 +01:00
parent ed74fb67f2
commit 4e73a81d4b
96 changed files with 3996 additions and 3418 deletions
+1 -1
View File
@@ -77,7 +77,7 @@ export function CommandPalette() {
return (
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogHeader className="sr-only">
<DialogHeader className="sr-only print:hidden">
<DialogTitle>
<Trans>Builder Command Palette</Trans>
</DialogTitle>
+2 -9
View File
@@ -106,7 +106,7 @@ export const ResumePreview = ({ showPageNumbers, pageClassName, className, ...pr
const pageNumber = pageIndex + 1;
return (
<div key={pageIndex} className="relative">
<div key={pageIndex} data-page-index={pageIndex} className="relative">
{showPageNumbers && totalNumberOfPages > 1 && (
<div className="absolute -top-6 left-0">
<span className="font-medium text-foreground text-xs">
@@ -117,14 +117,7 @@ export const ResumePreview = ({ showPageNumbers, pageClassName, className, ...pr
</div>
)}
<div
className={cn(
`page page-${pageIndex}`,
pageIndex > 0 && "print:break-before-page",
styles.page,
pageClassName,
)}
>
<div className={cn(`page page-${pageIndex}`, styles.page, pageClassName)}>
<TemplateComponent pageIndex={pageIndex} pageLayout={pageLayout} />
</div>
</div>
@@ -1,5 +1,5 @@
import { match } from "ts-pattern";
import type { CustomSectionItem, SectionType } from "@/schema/resume/data";
import type { CustomSectionItem, SectionItem, SectionType } from "@/schema/resume/data";
import { cn } from "@/utils/style";
import { useResumeStore } from "../store/resume";
import { AwardsItem } from "./items/awards-item";
@@ -25,38 +25,22 @@ type SectionComponentProps = {
// Helper to render item component based on type
function renderItemByType(type: SectionType, item: CustomSectionItem, itemClassName?: string) {
return match(type)
.with("profiles", () => (
<ProfilesItem {...(item as Parameters<typeof ProfilesItem>[0])} className={itemClassName} />
))
.with("experience", () => (
<ExperienceItem {...(item as Parameters<typeof ExperienceItem>[0])} className={itemClassName} />
))
.with("education", () => (
<EducationItem {...(item as Parameters<typeof EducationItem>[0])} className={itemClassName} />
))
.with("projects", () => (
<ProjectsItem {...(item as Parameters<typeof ProjectsItem>[0])} className={itemClassName} />
))
.with("skills", () => <SkillsItem {...(item as Parameters<typeof SkillsItem>[0])} className={itemClassName} />)
.with("languages", () => (
<LanguagesItem {...(item as Parameters<typeof LanguagesItem>[0])} className={itemClassName} />
))
.with("interests", () => (
<InterestsItem {...(item as Parameters<typeof InterestsItem>[0])} className={itemClassName} />
))
.with("awards", () => <AwardsItem {...(item as Parameters<typeof AwardsItem>[0])} className={itemClassName} />)
.with("profiles", () => <ProfilesItem {...(item as SectionItem<"profiles">)} className={itemClassName} />)
.with("experience", () => <ExperienceItem {...(item as SectionItem<"experience">)} className={itemClassName} />)
.with("education", () => <EducationItem {...(item as SectionItem<"education">)} className={itemClassName} />)
.with("projects", () => <ProjectsItem {...(item as SectionItem<"projects">)} className={itemClassName} />)
.with("skills", () => <SkillsItem {...(item as SectionItem<"skills">)} className={itemClassName} />)
.with("languages", () => <LanguagesItem {...(item as SectionItem<"languages">)} className={itemClassName} />)
.with("interests", () => <InterestsItem {...(item as SectionItem<"interests">)} className={itemClassName} />)
.with("awards", () => <AwardsItem {...(item as SectionItem<"awards">)} className={itemClassName} />)
.with("certifications", () => (
<CertificationsItem {...(item as Parameters<typeof CertificationsItem>[0])} className={itemClassName} />
<CertificationsItem {...(item as SectionItem<"certifications">)} className={itemClassName} />
))
.with("publications", () => (
<PublicationsItem {...(item as Parameters<typeof PublicationsItem>[0])} className={itemClassName} />
))
.with("volunteer", () => (
<VolunteerItem {...(item as Parameters<typeof VolunteerItem>[0])} className={itemClassName} />
))
.with("references", () => (
<ReferencesItem {...(item as Parameters<typeof ReferencesItem>[0])} className={itemClassName} />
<PublicationsItem {...(item as SectionItem<"publications">)} className={itemClassName} />
))
.with("volunteer", () => <VolunteerItem {...(item as SectionItem<"volunteer">)} className={itemClassName} />)
.with("references", () => <ReferencesItem {...(item as SectionItem<"references">)} className={itemClassName} />)
.exhaustive();
}
@@ -186,7 +170,10 @@ export function getSectionComponent(
style={{ gridTemplateColumns: `repeat(${customSection.columns}, 1fr)` }}
>
{visibleItems.map((item) => (
<div key={item.id} className={cn(`section-item section-item-${customSection.type}`)}>
<div
key={item.id}
className={cn(`section-item section-item-${customSection.type} print:break-inside-avoid`)}
>
{renderItemByType(customSection.type, item, itemClassName)}
</div>
))}
@@ -194,6 +181,7 @@ export function getSectionComponent(
</section>
);
};
return CustomSectionComponent;
});
}
@@ -10,21 +10,27 @@ type AwardsItemProps = SectionItem<"awards"> & {
export function AwardsItem({ className, ...item }: AwardsItemProps) {
return (
<div className={cn("awards-item", className)}>
{/* Header */}
<div className="section-item-header awards-item-header">
{/* Row 1 */}
<div className="flex items-center justify-between">
<p className="section-item-title awards-item-title">
<strong>{item.title}</strong>
</p>
<p className="section-item-metadata awards-item-date text-right">{item.date}</p>
<strong className="section-item-title awards-item-title">{item.title}</strong>
<span className="section-item-metadata awards-item-date text-right">{item.date}</span>
</div>
{/* Row 2 */}
<div className="flex items-center justify-between">
<p className="section-item-metadata awards-item-awarder">{item.awarder}</p>
<span className="section-item-metadata awards-item-awarder">{item.awarder}</span>
</div>
</div>
{/* Description */}
<div className="section-item-description awards-item-description">
<TiptapContent content={item.description} />
</div>
<div className="section-item-link awards-item-link">
{/* Website */}
<div className="section-item-website awards-item-website">
<PageLink {...item.website} label={item.website.label} />
</div>
</div>
@@ -10,21 +10,27 @@ type CertificationsItemProps = SectionItem<"certifications"> & {
export function CertificationsItem({ className, ...item }: CertificationsItemProps) {
return (
<div className={cn("certifications-item", className)}>
{/* Header */}
<div className="section-item-header certifications-item-header">
{/* Row 1 */}
<div className="flex items-center justify-between">
<p className="section-item-title certifications-item-title">
<strong>{item.title}</strong>
</p>
<p className="section-item-metadata certifications-item-date text-right">{item.date}</p>
<strong className="section-item-title certifications-item-title">{item.title}</strong>
<span className="section-item-metadata certifications-item-date text-right">{item.date}</span>
</div>
{/* Row 2 */}
<div className="flex items-center justify-between">
<p className="section-item-metadata certifications-item-issuer">{item.issuer}</p>
<span className="section-item-metadata certifications-item-issuer">{item.issuer}</span>
</div>
</div>
{/* Description */}
<div className="section-item-description certifications-item-description">
<TiptapContent content={item.description} />
</div>
<div className="section-item-link certifications-item-link">
{/* Website */}
<div className="section-item-website certifications-item-website">
<PageLink {...item.website} label={item.website.label} />
</div>
</div>
@@ -10,26 +10,32 @@ type EducationItemProps = SectionItem<"education"> & {
export function EducationItem({ className, ...item }: EducationItemProps) {
return (
<div className={cn("education-item", className)}>
{/* Header */}
<div className="section-item-header education-item-header mb-2">
{/* Row 1 */}
<div className="flex items-center justify-between">
<p className="section-item-title education-item-title">
<strong>{item.school}</strong>
</p>
<p className="section-item-metadata education-item-degree-grade text-right">
<strong className="section-item-title education-item-title">{item.school}</strong>
<span className="section-item-metadata education-item-degree-grade text-right">
{[item.degree, item.grade].filter(Boolean).join(" • ")}
</p>
</span>
</div>
{/* Row 2 */}
<div className="flex items-center justify-between">
<p className="section-item-metadata education-item-area">{item.area}</p>
<p className="section-item-metadata education-item-location-period text-right">
<span className="section-item-metadata education-item-area">{item.area}</span>
<span className="section-item-metadata education-item-location-period text-right">
{[item.location, item.period].filter(Boolean).join(" • ")}
</p>
</span>
</div>
</div>
{/* Description */}
<div className="section-item-description education-item-description">
<TiptapContent content={item.description} />
</div>
<div className="section-item-link education-item-link">
{/* Website */}
<div className="section-item-website education-item-website">
<PageLink {...item.website} label={item.website.label} />
</div>
</div>
@@ -10,22 +10,28 @@ type ExperienceItemProps = SectionItem<"experience"> & {
export function ExperienceItem({ className, ...item }: ExperienceItemProps) {
return (
<div className={cn("experience-item", className)}>
{/* Header */}
<div className="section-item-header experience-item-header">
{/* Row 1 */}
<div className="flex items-center justify-between">
<p className="section-item-title experience-item-title">
<strong>{item.company}</strong>
</p>
<p className="section-item-metadata experience-item-location text-right">{item.location}</p>
<strong className="section-item-title experience-item-title">{item.company}</strong>
<span className="section-item-metadata experience-item-location text-right">{item.location}</span>
</div>
{/* Row 2 */}
<div className="flex items-center justify-between">
<p className="section-item-metadata experience-item-position">{item.position}</p>
<p className="section-item-metadata experience-item-period text-right">{item.period}</p>
<span className="section-item-metadata experience-item-position">{item.position}</span>
<span className="section-item-metadata experience-item-period text-right">{item.period}</span>
</div>
</div>
{/* Description */}
<div className="section-item-description experience-item-description">
<TiptapContent content={item.description} />
</div>
<div className="section-item-link experience-item-link">
{/* Website */}
<div className="section-item-website experience-item-website">
<PageLink {...item.website} label={item.website.label} />
</div>
</div>
@@ -9,14 +9,14 @@ type InterestsItemProps = SectionItem<"interests"> & {
export function InterestsItem({ className, ...item }: InterestsItemProps) {
return (
<div className={cn("interests-item", className)}>
{/* Header */}
<div className="section-item-header flex items-center gap-x-1.5">
<PageIcon icon={item.icon} className="section-item-icon interests-item-icon shrink-0" />
<p className="section-item-title interests-item-name">
<strong>{item.name}</strong>
</p>
<PageIcon icon={item.icon} className="section-item-icon interests-item-icon" />
<strong className="section-item-title interests-item-name">{item.name}</strong>
</div>
<p className="section-item-keywords interests-item-keywords opacity-80">{item.keywords.join(", ")}</p>
{/* Keywords */}
<span className="section-item-keywords interests-item-keywords opacity-80">{item.keywords.join(", ")}</span>
</div>
);
}
@@ -9,13 +9,16 @@ type LanguagesItemProps = SectionItem<"languages"> & {
export function LanguagesItem({ className, ...item }: LanguagesItemProps) {
return (
<div className={cn("languages-item", className)}>
<div className="section-item-header">
<p className="section-item-title languages-item-name">
<strong>{item.language}</strong>
</p>
<p className="section-item-metadata languages-item-fluency opacity-80">{item.fluency}</p>
{/* Header */}
<div className="section-item-header flex flex-col">
{/* Row 1 */}
<strong className="section-item-title languages-item-name">{item.language}</strong>
{/* Row 2 */}
<span className="section-item-metadata languages-item-fluency opacity-80">{item.fluency}</span>
</div>
{/* Level */}
<PageLevel level={item.level} className="section-item-level languages-item-level" />
</div>
);
@@ -10,17 +10,17 @@ type ProfilesItemProps = SectionItem<"profiles"> & {
export function ProfilesItem({ className, ...item }: ProfilesItemProps) {
return (
<div className={cn("profiles-item", className)}>
{/* Header */}
<div className="section-item-header flex items-center gap-x-1.5">
<PageIcon icon={item.icon} className="section-item-icon profiles-item-icon shrink-0" />
<p className="section-item-title profiles-item-network">
<strong>{item.network}</strong>
</p>
<PageIcon icon={item.icon} className="section-item-icon profiles-item-icon" />
<strong className="section-item-title profiles-item-network">{item.network}</strong>
</div>
{/* Website */}
<PageLink
{...item.website}
label={item.website.label || item.username}
className="section-item-link profiles-item-link"
className="section-item-website profiles-item-website"
/>
</div>
);
@@ -10,18 +10,22 @@ type ProjectsItemProps = SectionItem<"projects"> & {
export function ProjectsItem({ className, ...item }: ProjectsItemProps) {
return (
<div className={cn("projects-item", className)}>
{/* Header */}
<div className="section-item-header projects-item-header">
{/* Row 1 */}
<div className="flex items-center justify-between">
<p className="section-item-title projects-item-title">
<strong>{item.name}</strong>
</p>
<p className="section-item-metadata projects-item-period text-right">{item.period}</p>
<strong className="section-item-title projects-item-title">{item.name}</strong>
<span className="section-item-metadata projects-item-period text-right">{item.period}</span>
</div>
</div>
{/* Description */}
<div className="section-item-description projects-item-description">
<TiptapContent content={item.description} />
</div>
<div className="section-item-link projects-item-link">
{/* Website */}
<div className="section-item-website projects-item-website">
<PageLink {...item.website} label={item.website.label} />
</div>
</div>
@@ -10,21 +10,27 @@ type PublicationsItemProps = SectionItem<"publications"> & {
export function PublicationsItem({ className, ...item }: PublicationsItemProps) {
return (
<div className={cn("publications-item", className)}>
{/* Header */}
<div className="section-item-header publications-item-header">
{/* Row 1 */}
<div className="flex items-center justify-between">
<p className="section-item-title publications-item-title">
<strong>{item.title}</strong>
</p>
<p className="section-item-metadata publications-item-date text-right">{item.date}</p>
<strong className="section-item-title publications-item-title">{item.title}</strong>
<span className="section-item-metadata publications-item-date text-right">{item.date}</span>
</div>
{/* Row 2 */}
<div className="flex items-center justify-between">
<p className="section-item-metadata publications-item-publisher">{item.publisher}</p>
<span className="section-item-metadata publications-item-publisher">{item.publisher}</span>
</div>
</div>
{/* Description */}
<div className="section-item-description publications-item-description">
<TiptapContent content={item.description} />
</div>
<div className="section-item-link publications-item-link">
{/* Website */}
<div className="section-item-website publications-item-website">
<PageLink {...item.website} label={item.website.label} />
</div>
</div>
@@ -10,20 +10,37 @@ type ReferencesItemProps = SectionItem<"references"> & {
export function ReferencesItem({ className, ...item }: ReferencesItemProps) {
return (
<div className={cn("references-item", className)}>
{/* Header */}
<div className="section-item-header references-item-header">
{/* Row 1 */}
<div className="flex items-center justify-between">
<p className="section-item-title references-item-name">
<strong>{item.name}</strong>
</p>
<strong className="section-item-title references-item-name">{item.name}</strong>
</div>
{/* Row 2 */}
<div className="flex items-center justify-between">
<span className="section-item-metadata references-item-position">{item.position}</span>
</div>
<p className="section-item-metadata references-item-position">{item.position}</p>
</div>
{/* Description */}
<div className="section-item-description references-item-description">
<TiptapContent content={item.description} />
</div>
{/* Footer */}
<div className="section-item-footer references-item-footer flex flex-col">
<p className="section-item-metadata references-item-phone">{item.phone}</p>
<PageLink {...item.website} label={item.website.label} className="section-item-link references-item-link" />
{/* Row 1 */}
<div className="flex items-center justify-between">
<span className="section-item-metadata references-item-phone">{item.phone}</span>
</div>
{/* Row 2 */}
<PageLink
{...item.website}
label={item.website.label}
className="section-item-website references-item-website"
/>
</div>
</div>
);
@@ -10,18 +10,23 @@ type SkillsItemProps = SectionItem<"skills"> & {
export function SkillsItem({ className, ...item }: SkillsItemProps) {
return (
<div className={cn("skills-item", className)}>
{/* Header */}
<div className="section-item-header flex items-center gap-x-1.5">
<PageIcon icon={item.icon} className="section-item-icon skills-item-icon shrink-0" />
<p className="section-item-title skills-item-name">
<strong>{item.name}</strong>
</p>
<PageIcon icon={item.icon} className="section-item-icon skills-item-icon" />
<strong className="section-item-title skills-item-name">{item.name}</strong>
</div>
<div>
<p className="section-item-metadata skills-item-proficiency opacity-80">{item.proficiency}</p>
<small className="section-item-keywords skills-item-keywords">{item.keywords.join(", ")}</small>
{/* Proficiency */}
<div className="flex items-center justify-between">
<span className="section-item-metadata skills-item-proficiency">{item.proficiency}</span>
</div>
{/* Keywords */}
<div className="flex items-center justify-between">
<span className="section-item-keywords skills-item-keywords opacity-80">{item.keywords.join(", ")}</span>
</div>
{/* Level */}
<PageLevel level={item.level} className="section-item-level skills-item-level" />
</div>
);
@@ -10,21 +10,27 @@ type VolunteerItemProps = SectionItem<"volunteer"> & {
export function VolunteerItem({ className, ...item }: VolunteerItemProps) {
return (
<div className={cn("volunteer-item", className)}>
{/* Header */}
<div className="section-item-header volunteer-item-header">
{/* Row 1 */}
<div className="flex items-center justify-between">
<p className="section-item-title volunteer-item-title">
<strong>{item.organization}</strong>
</p>
<p className="section-item-metadata volunteer-item-period text-right">{item.period}</p>
<strong className="section-item-title volunteer-item-title">{item.organization}</strong>
<span className="section-item-metadata volunteer-item-period text-right">{item.period}</span>
</div>
{/* Row 2 */}
<div className="flex items-center justify-between">
<p className="section-item-metadata volunteer-item-location">{item.location}</p>
<span className="section-item-metadata volunteer-item-location">{item.location}</span>
</div>
</div>
{/* Description */}
<div className="section-item-description volunteer-item-description">
<TiptapContent content={item.description} />
</div>
<div className="section-item-link volunteer-item-link">
{/* Website */}
<div className="section-item-website volunteer-item-website">
<PageLink {...item.website} label={item.website.label} />
</div>
</div>
+1 -1
View File
@@ -10,7 +10,7 @@ export function PageIcon({ icon, className }: { icon: string; className?: string
return (
<i
className={cn("ph", `ph-${icon}`, className)}
className={cn("ph shrink-0", `ph-${icon}`, className)}
style={{ fontSize: `${iconContext.size}px`, color: iconContext.color }}
/>
);
+1 -1
View File
@@ -9,5 +9,5 @@ type Props = React.ComponentProps<"div"> & {
export function PageLevel({ level, className, ...props }: Props) {
const { icon, type } = useResumeStore((state) => state.resume.data.metadata.design.level);
return <LevelDisplay icon={icon} type={type} level={level} className={cn("h-4", className)} {...props} />;
return <LevelDisplay icon={icon} type={type} level={level} className={cn("h-6", className)} {...props} />;
}
+1 -1
View File
@@ -10,7 +10,7 @@ export function PageLink({ url, label, className }: Props) {
if (!url) return null;
return (
<a href={url} target="_blank" rel="noopener" className={cn("inline-block text-wrap", className)}>
<a href={url} target="_blank" rel="noopener" className={cn("inline-block text-wrap break-all", className)}>
{label || url}
</a>
);
@@ -19,14 +19,14 @@ export function PageSection<T extends SectionType>({ type, className, children }
return (
<section className={cn(`page-section page-section-${type}`, className)}>
<h6 className="mb-1 text-(--page-primary-color)">{section.title || getSectionTitle(type)}</h6>
<h6 className="mb-2 text-(--page-primary-color)">{section.title || getSectionTitle(type)}</h6>
<div
className="section-content grid gap-x-(--page-gap-x) gap-y-(--page-gap-y)"
style={{ gridTemplateColumns: `repeat(${section.columns}, 1fr)` }}
>
{items.map((item) => (
<div key={item.id} className={cn(`section-item section-item-${type} *:space-y-1`)}>
<div key={item.id} className={cn(`section-item section-item-${type} print:break-inside-avoid`)}>
{children(item)}
</div>
))}
+4 -4
View File
@@ -25,9 +25,8 @@ type ResumeStore = ResumeStoreState & ResumeStoreActions;
const controller = new AbortController();
const signal = controller.signal;
const _syncResume = async (resume: Resume | null) => {
if (!resume) return;
await orpc.resume.update.call({ id: resume.id, data: resume.data }, { signal });
const _syncResume = (resume: Resume) => {
orpc.resume.update.call({ id: resume.id, data: resume.data }, { signal });
};
const syncResume = debounce(_syncResume, 500, { signal });
@@ -49,12 +48,13 @@ export const useResumeStore = create<ResumeStore>()(
updateResumeData: (fn) => {
set((state) => {
if (!state.resume) return state;
if (state.resume.isLocked) {
errorToastId = toast.error(t`This resume is locked and cannot be updated.`, { id: errorToastId });
return state;
}
fn(state.resume.data as WritableDraft<ResumeData>);
fn(state.resume.data);
syncResume(current(state.resume));
});
},
@@ -8,6 +8,26 @@ import { useResumeStore } from "../store/resume";
import type { TemplateProps } from "./types";
const sectionClassName = cn(
// Heading Decoration in Sidebar Layout
"group-data-[layout=sidebar]:[&>h6]:px-4",
"group-data-[layout=sidebar]:[&>h6]:relative",
"group-data-[layout=sidebar]:[&>h6]:inline-flex",
"group-data-[layout=sidebar]:[&>h6]:items-center",
"group-data-[layout=sidebar]:[&>h6]:before:content-['']",
"group-data-[layout=sidebar]:[&>h6]:before:absolute",
"group-data-[layout=sidebar]:[&>h6]:before:left-0",
"group-data-[layout=sidebar]:[&>h6]:before:rounded-full",
"group-data-[layout=sidebar]:[&>h6]:before:size-2",
"group-data-[layout=sidebar]:[&>h6]:before:border",
"group-data-[layout=sidebar]:[&>h6]:before:border-(--page-primary-color)",
"group-data-[layout=sidebar]:[&>h6]:after:content-['']",
"group-data-[layout=sidebar]:[&>h6]:after:absolute",
"group-data-[layout=sidebar]:[&>h6]:after:right-0",
"group-data-[layout=sidebar]:[&>h6]:after:rounded-full",
"group-data-[layout=sidebar]:[&>h6]:after:size-2",
"group-data-[layout=sidebar]:[&>h6]:after:border",
"group-data-[layout=sidebar]:[&>h6]:after:border-(--page-primary-color)",
// Section in Sidebar Layout
"group-data-[layout=sidebar]:[&_.section-item-header>div]:flex-col",
"group-data-[layout=sidebar]:[&_.section-item-header>div]:items-start",
+15 -3
View File
@@ -18,6 +18,10 @@ const sectionClassName = cn(
// Icon Colors in Sidebar Layout
"group-data-[layout=sidebar]:[&_.section-item_i]:text-(--page-background-color)!",
// Level Display in Sidebar Layout
"group-data-[layout=sidebar]:[&_.section-item-level>div]:bg-(--page-background-color)",
"group-data-[layout=sidebar]:[&_.section-item-level>div]:text-(--page-background-color)",
// Section Item Header in Sidebar Layout
"group-data-[layout=sidebar]:[&_.section-item-header>div]:flex-col",
"group-data-[layout=sidebar]:[&_.section-item-header>div]:items-start",
@@ -32,10 +36,18 @@ export function ChikoritaTemplate({ pageIndex, pageLayout }: TemplateProps) {
return (
<div className="template-chikorita page-content">
{/* Sidebar Background */}
{!fullWidth && (
<div className="page-sidebar-background absolute inset-y-0 right-0 z-0 w-(--page-sidebar-width) shrink-0 bg-(--page-primary-color)" />
)}
{isFirstPage && <Header />}
<div className="flex">
<main data-layout="main" className="group page-main flex-1 space-y-4 px-(--page-margin-x) py-(--page-margin-y)">
<main
data-layout="main"
className="group page-main z-10 flex-1 space-y-4 px-(--page-margin-x) py-(--page-margin-y)"
>
{main.map((section) => {
const Component = getSectionComponent(section, { sectionClassName });
return <Component key={section} id={section} />;
@@ -45,7 +57,7 @@ export function ChikoritaTemplate({ pageIndex, pageLayout }: TemplateProps) {
{!fullWidth && (
<aside
data-layout="sidebar"
className="group page-sidebar w-(--page-sidebar-width) shrink-0 space-y-4 overflow-x-hidden bg-(--page-primary-color) px-(--page-margin-x) pb-(--page-margin-y) text-(--page-background-color)"
className="group page-sidebar z-10 w-(--page-sidebar-width) shrink-0 space-y-4 overflow-x-hidden px-(--page-margin-x) py-(--page-margin-y) text-(--page-background-color)"
>
{sidebar.map((section) => {
const Component = getSectionComponent(section, { sectionClassName });
@@ -111,7 +123,7 @@ function Header() {
</div>
</div>
<div className="w-(--page-sidebar-width) shrink-0 bg-(--page-primary-color)" />
<div className="w-(--page-sidebar-width) shrink-0" />
</div>
);
}
+32 -21
View File
@@ -13,6 +13,13 @@ const sectionClassName = cn(
// Section Item Header in Sidebar Layout
"group-data-[layout=sidebar]:[&_.section-item-header>div]:flex-col",
"group-data-[layout=sidebar]:[&_.section-item-header>div]:items-start",
// Decoration Line in Section Item Header
"group-data-[layout=main]:[&_.section-item-header]:pl-2",
"group-data-[layout=main]:[&_.section-item-header]:py-0.5",
"group-data-[layout=main]:[&_.section-item-header]:-ml-2.5",
"group-data-[layout=main]:[&_.section-item-header]:border-l-2",
"group-data-[layout=main]:[&_.section-item-header]:border-(--page-primary-color)",
);
/**
@@ -27,32 +34,36 @@ export function DitgarTemplate({ pageIndex, pageLayout }: TemplateProps) {
});
return (
<div className="template-ditgar page-content grid min-h-[inherit] grid-cols-3">
<div
data-layout="sidebar"
className={cn("sidebar group flex flex-col", !(isFirstPage || !fullWidth) && "hidden")}
>
{isFirstPage && <Header />}
<div className="template-ditgar page-content">
{/* Sidebar Background */}
{!fullWidth && (
<div className="page-sidebar-background absolute inset-y-0 left-0 z-0 w-(--page-sidebar-width) shrink-0 bg-(--page-primary-color)/20" />
)}
<div className="flex-1 space-y-4 bg-(--page-primary-color)/20 px-(--page-margin-x) py-(--page-margin-y)">
{sidebar.map((section) => {
const Component = getSectionComponent(section, { sectionClassName });
return <Component key={section} id={section} />;
})}
</div>
</div>
<div className="flex">
<aside data-layout="sidebar" className="sidebar group z-10 flex w-(--page-sidebar-width) shrink-0 flex-col">
{isFirstPage && <Header />}
<div data-layout="main" className={cn("main group", !fullWidth ? "col-span-2" : "col-span-3")}>
{isFirstPage && <SummaryComponent id="summary" />}
<div className="space-y-4 px-(--page-margin-x) py-(--page-margin-y)">
{main
.filter((section) => section !== "summary")
.map((section) => {
<div className="flex-1 space-y-4 px-(--page-margin-x) py-(--page-margin-y)">
{sidebar.map((section) => {
const Component = getSectionComponent(section, { sectionClassName });
return <Component key={section} id={section} />;
})}
</div>
</div>
</aside>
<main data-layout="main" className={cn("main group z-10", !fullWidth ? "col-span-2" : "col-span-3")}>
{isFirstPage && <SummaryComponent id="summary" />}
<div className="space-y-4 px-(--page-margin-x) py-(--page-margin-y)">
{main
.filter((section) => section !== "summary")
.map((section) => {
const Component = getSectionComponent(section, { sectionClassName });
return <Component key={section} id={section} />;
})}
</div>
</main>
</div>
</div>
);
+24 -14
View File
@@ -25,23 +25,33 @@ export function GengarTemplate({ pageIndex, pageLayout }: TemplateProps) {
return (
<div className="template-gengar page-content">
{/* Sidebar Background */}
{(!fullWidth || isFirstPage) && (
<div className="page-sidebar-background absolute inset-y-0 left-0 z-0 w-(--page-sidebar-width) shrink-0 bg-(--page-primary-color)/20" />
)}
<div className="flex">
<div data-layout="sidebar" className="group page-sidebar flex w-(--page-sidebar-width) shrink-0 flex-col">
{isFirstPage && <Header />}
{(!fullWidth || isFirstPage) && (
<aside
data-layout="sidebar"
className="group page-sidebar z-10 flex w-(--page-sidebar-width) shrink-0 flex-col"
>
{isFirstPage && <Header />}
{!fullWidth && (
<aside className="shrink-0 space-y-4 overflow-x-hidden bg-(--page-primary-color)/20 px-(--page-margin-x) pt-4 pb-(--page-margin-y)">
{sidebar
.filter((section) => section !== "summary")
.map((section) => {
const Component = getSectionComponent(section, { sectionClassName });
return <Component key={section} id={section} />;
})}
</aside>
)}
</div>
{!fullWidth && (
<div className="shrink-0 space-y-4 overflow-x-hidden px-(--page-margin-x) pt-4 pb-(--page-margin-y)">
{sidebar
.filter((section) => section !== "summary")
.map((section) => {
const Component = getSectionComponent(section, { sectionClassName });
return <Component key={section} id={section} />;
})}
</div>
)}
</aside>
)}
<main data-layout="main" className="group page-main">
<main data-layout="main" className="group page-main z-10">
{isFirstPage && (
<PageSummary
className={cn(
+7 -2
View File
@@ -25,10 +25,15 @@ export function GlalieTemplate({ pageIndex, pageLayout }: TemplateProps) {
return (
<div className="template-glalie page-content">
{/* Sidebar Background */}
{(!fullWidth || isFirstPage) && (
<div className="page-sidebar-background absolute inset-y-0 left-0 z-0 w-(--page-sidebar-width) shrink-0 bg-(--page-primary-color)/20" />
)}
<div className="flex">
<aside
data-layout="sidebar"
className="group page-sidebar flex w-(--page-sidebar-width) shrink-0 flex-col space-y-4 bg-(--page-primary-color)/20 px-(--page-margin-x) py-(--page-margin-y)"
className="group page-sidebar flex w-(--page-sidebar-width) shrink-0 flex-col space-y-4 px-(--page-margin-x) py-(--page-margin-y)"
>
{isFirstPage && <Header />}
@@ -60,7 +65,7 @@ function Header() {
return (
<div className="page-header relative flex">
<div className="flex w-full shrink-0 flex-col items-center justify-center gap-y-2">
<div className="flex w-full shrink-0 flex-col items-center justify-center gap-y-3">
<PagePicture />
<div className="text-center">
+2 -2
View File
@@ -9,7 +9,7 @@ import type { TemplateProps } from "./types";
const sectionClassName = cn(
// Container
"rounded-(--container-border-radius) border border-(--page-text-color)/10 bg-(--page-background-color) p-4 shadow-md",
"rounded-(--container-border-radius) border border-(--page-text-color)/10 bg-(--page-background-color) p-4",
// Section Heading
"[&>h6]:-mt-(--heading-negative-margin) [&>h6]:max-w-fit [&>h6]:bg-(--page-background-color) [&>h6]:px-4 [&>h6]:pb-0.5",
@@ -63,7 +63,7 @@ function Header() {
<div
className={cn(
"page-header flex items-center gap-x-4",
"rounded-(--picture-border-radius) border border-(--page-text-color)/10 bg-(--page-background-color) p-4 shadow-md",
"rounded-(--picture-border-radius) border border-(--page-text-color)/10 bg-(--page-background-color) p-4",
)}
>
<PagePicture />
+1 -1
View File
@@ -11,7 +11,7 @@ function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
data-slot="input-group"
role="group"
className={cn(
"group/input-group relative flex h-9 w-full min-w-0 items-center rounded-md border border-input shadow-xs outline-none transition-[color,box-shadow] in-data-[slot=combobox-content]:focus-within:border-ring has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-start]]:h-auto has-[>textarea]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:flex-col has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-[3px] has-[[data-slot][aria-invalid=true]]:ring-destructive/20 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-start]]:[&>input]:pl-1.5",
"group/input-group relative flex h-9 w-full min-w-0 items-center rounded-md border border-input shadow-xs outline-none transition-[color,box-shadow] in-data-[slot=combobox-content]:focus-within:border-ring has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-start]]:h-auto has-[>textarea]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:flex-col has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-[3px] has-[[data-slot][aria-invalid=true]]:ring-destructive/20 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-start]]:[&>input]:pl-1.5",
className,
)}
{...props}
+1 -1
View File
@@ -8,7 +8,7 @@ const Toaster = ({ ...props }: ToasterProps) => {
return (
<Sonner
theme={theme as ToasterProps["theme"]}
className="toaster group"
className="toaster group print:hidden"
icons={{
success: <CheckCircleIcon className="size-4" />,
info: <InfoIcon className="size-4" />,