{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "work-experience",
  "title": "Work Experience",
  "author": "nghia.ly <physicalmeans@gmail.com>",
  "description": "Display work experiences with role details, company logos, and durations.",
  "dependencies": [
    "react-markdown",
    "lucide-react"
  ],
  "registryDependencies": [
    "collapsible",
    "separator",
    "https://lyminhnghia.github.io/r/chevrons-up-down-icon.json",
    "https://lyminhnghia.github.io/r/typography.json"
  ],
  "files": [
    {
      "path": "src/registry/components/work-experience/work-experience.tsx",
      "content": "\"use client\"\n\nimport { BriefcaseBusinessIcon, InfinityIcon } from \"lucide-react\"\nimport { type ComponentProps, useCallback, useRef } from \"react\"\nimport ReactMarkdown from \"react-markdown\"\n\nimport {\n  Collapsible,\n  CollapsibleContent,\n  CollapsibleTrigger,\n} from \"@/components/ui/collapsible\"\nimport { Separator } from \"@/components/ui/separator\"\nimport { cn } from \"@/lib/utils\"\nimport type { ChevronsUpDownIconHandle } from \"@/registry/components/chevrons-up-down-icon\"\nimport { ChevronsUpDownIcon } from \"@/registry/components/chevrons-up-down-icon\"\n\nexport type ExperiencePositionItemType = {\n  /** Unique identifier for the position */\n  id: string\n  /** The job title or position name */\n  title: string\n  /**\n   * Employment period of the position.\n   * Use \"MM.YYYY\" or \"YYYY\" format. Omit `end` for current roles.\n   */\n  employmentPeriod: {\n    /** Start date (e.g., \"10.2022\" or \"2020\"). */\n    start: string\n    /** End date; leave undefined for \"Present\". */\n    end?: string\n  }\n  /** The type of employment (e.g., \"Full-time\", \"Part-time\", \"Contract\") */\n  employmentType?: string\n  /** A brief description of the position or responsibilities */\n  description?: string\n  /** An icon representing the position */\n  icon?: React.ReactNode\n  /** A list of skills associated with the position */\n  skills?: string[]\n  /** Indicates if the position details are expanded in the UI */\n  isExpanded?: boolean\n}\n\nexport type ExperienceItemType = {\n  /** Unique identifier for the experience item */\n  id: string\n  /** Name of the company where the experience was gained */\n  companyName: string\n  /** URL or path to the company's logo image */\n  companyLogo?: string\n  /** URL to the company's website. */\n  companyWebsite?: string\n  /**\n   * List of positions held at the company\n   * @fumadocsHref #experiencepositionitemtype\n   * */\n  positions: ExperiencePositionItemType[]\n  /** Indicates if this is the user's current employer */\n  isCurrentEmployer?: boolean\n}\n\nexport type WorkExperienceProps = {\n  className?: string\n  /** @fumadocsHref #experienceitemtype */\n  experiences: ExperienceItemType[]\n}\n\nexport function WorkExperience({\n  className,\n  experiences,\n}: WorkExperienceProps) {\n  return (\n    <div className={cn(\"bg-background px-4 text-foreground\", className)}>\n      {experiences.map((experience) => (\n        <ExperienceItem key={experience.id} experience={experience} />\n      ))}\n    </div>\n  )\n}\n\nexport type ExperienceItemProps = {\n  experience: ExperienceItemType\n}\n\nexport function ExperienceItem({ experience }: ExperienceItemProps) {\n  return (\n    <div className=\"space-y-4 py-4\">\n      <div className=\"not-prose flex items-center gap-3\">\n        <div className=\"flex size-6 shrink-0 items-center justify-center\">\n          {experience.companyLogo ? (\n            <img\n              src={experience.companyLogo}\n              alt={experience.companyName}\n              className=\"size-6 rounded-full\"\n              aria-hidden\n            />\n          ) : (\n            <span className=\"flex size-2 rounded-full bg-zinc-300 dark:bg-zinc-600\" />\n          )}\n        </div>\n\n        <h3 className=\"text-lg leading-snug font-semibold\">\n          {experience.companyWebsite ? (\n            <a\n              className=\"underline-offset-4 hover:underline\"\n              href={experience.companyWebsite}\n              target=\"_blank\"\n              rel=\"noopener noreferrer\"\n            >\n              {experience.companyName}\n            </a>\n          ) : (\n            experience.companyName\n          )}\n        </h3>\n\n        {experience.isCurrentEmployer && (\n          <span className=\"relative flex items-center justify-center\">\n            <span className=\"absolute inline-flex size-3 animate-ping rounded-full bg-sky-500 opacity-50\" />\n            <span className=\"relative inline-flex size-2 rounded-full bg-sky-500\" />\n            <span className=\"sr-only\">Current Employer</span>\n          </span>\n        )}\n      </div>\n\n      <div className=\"relative space-y-4 before:absolute before:left-3 before:h-full before:w-px before:bg-border\">\n        {experience.positions.map((position) => (\n          <ExperiencePositionItem key={position.id} position={position} />\n        ))}\n      </div>\n    </div>\n  )\n}\n\nexport type ExperiencePositionItemProps = {\n  position: ExperiencePositionItemType\n}\n\nexport function ExperiencePositionItem({\n  position,\n}: ExperiencePositionItemProps) {\n  const chevronsIconRef = useRef<ChevronsUpDownIconHandle>(null)\n\n  const handleOpenChange = useCallback((open: boolean) => {\n    const controls = chevronsIconRef.current\n    if (!controls) return\n\n    if (open) {\n      controls.startAnimation()\n    } else {\n      controls.stopAnimation()\n    }\n  }, [])\n\n  const { start, end } = position.employmentPeriod\n  const isOngoing = !end\n\n  return (\n    <Collapsible\n      defaultOpen={position.isExpanded}\n      onOpenChange={handleOpenChange}\n      disabled={!position.description}\n      asChild\n    >\n      <div className=\"relative last:before:absolute last:before:h-full last:before:w-4 last:before:bg-background\">\n        <CollapsibleTrigger\n          className={cn(\n            \"group/experience-position not-prose block w-full text-left select-none\",\n            \"relative before:absolute before:-top-1 before:-right-1 before:-bottom-1.5 before:left-7 before:rounded-lg hover:before:bg-muted/30\",\n            \"data-disabled:before:content-none\"\n          )}\n        >\n          <div className=\"relative z-1 mb-1 flex items-center gap-3\">\n            <div\n              className={cn(\n                \"flex size-6 shrink-0 items-center justify-center rounded-lg\",\n                \"bg-muted text-muted-foreground\",\n                \"border border-muted-foreground/15 ring-1 ring-line ring-offset-1 ring-offset-background\",\n                \"[&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\"\n              )}\n            >\n              {position.icon ?? <BriefcaseBusinessIcon />}\n            </div>\n\n            <h4 className=\"flex-1 text-base font-medium text-balance text-foreground\">\n              {position.title}\n            </h4>\n\n            <div className=\"shrink-0 text-muted-foreground group-disabled/experience-position:hidden [&_svg]:size-4\">\n              <ChevronsUpDownIcon ref={chevronsIconRef} duration={0.15} />\n            </div>\n          </div>\n\n          <div className=\"relative z-1 flex items-center gap-2 pl-9 text-sm text-muted-foreground\">\n            {position.employmentType && (\n              <>\n                <dl>\n                  <dt className=\"sr-only\">Employment Type</dt>\n                  <dd>{position.employmentType}</dd>\n                </dl>\n\n                <Separator\n                  className=\"data-vertical:h-4 data-vertical:self-center\"\n                  orientation=\"vertical\"\n                />\n              </>\n            )}\n\n            <dl>\n              <dt className=\"sr-only\">Employment Period</dt>\n              <dd className=\"flex items-center gap-0.5\">\n                <span>{start}</span>\n                <span className=\"font-mono\">—</span>\n                {isOngoing ? (\n                  <>\n                    <InfinityIcon className=\"size-4.5 translate-y-[0.5px]\" />\n                    <span className=\"sr-only\">Present</span>\n                  </>\n                ) : (\n                  <span>{end}</span>\n                )}\n              </dd>\n            </dl>\n          </div>\n        </CollapsibleTrigger>\n\n        <CollapsibleContent className=\"overflow-hidden\">\n          {position.description && (\n            <Prose className=\"pt-2 pl-9\">\n              <ReactMarkdown>{position.description}</ReactMarkdown>\n            </Prose>\n          )}\n        </CollapsibleContent>\n\n        {Array.isArray(position.skills) && position.skills.length > 0 && (\n          <ul className=\"not-prose flex flex-wrap gap-1.5 pt-3 pl-9\">\n            {position.skills.map((skill, index) => (\n              <li key={index} className=\"flex\">\n                <Skill>{skill}</Skill>\n              </li>\n            ))}\n          </ul>\n        )}\n      </div>\n    </Collapsible>\n  )\n}\n\nfunction Prose({ className, ...props }: ComponentProps<\"div\">) {\n  return (\n    <div\n      className={cn(\n        \"prose prose-sm max-w-none prose-site font-mono text-foreground prose-zinc dark:prose-invert\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n\nfunction Skill({ className, ...props }: ComponentProps<\"span\">) {\n  return (\n    <span\n      className={cn(\n        \"inline-flex items-center rounded-lg border bg-muted/50 px-1.5 py-0.5 font-mono text-xs text-muted-foreground\",\n        className\n      )}\n      {...props}\n    />\n  )\n}\n",
      "type": "registry:component"
    }
  ],
  "docs": "https://lyminhnghia.github.io/components/work-experience-component",
  "type": "registry:component"
}