{
  "$schema": "https://pdfx.akashpise.dev/schema/registry-item.json",
  "name": "list",
  "type": "registry:ui",
  "title": "List",
  "description": "List component with bullet, numbered, checklist, icon, multi-level, and descriptive variants",
  "files": [
    {
      "path": "components/pdfx/list/pdfx-list.tsx",
      "content": "import { Text as PDFText, View } from '@react-pdf/renderer';\nimport type { Style } from '@react-pdf/types';\nimport type React from 'react';\nimport { usePdfxTheme, useSafeMemo } from '../lib/pdfx-theme-context';\nimport { createListStyles } from './pdfx-list.styles';\nimport type { ListItem, ListVariant, PdfListProps } from './pdfx-list.types';\n\ntype Styles = ReturnType<typeof createListStyles>;\ntype GapProp = 'xs' | 'sm' | 'md';\n\nfunction getGapStyle(gap: GapProp, styles: Styles): Style {\n  if (gap === 'xs') return styles.itemRowGapXs;\n  if (gap === 'md') return styles.itemRowGapMd;\n  return styles.itemRowGapSm;\n}\n\nfunction buildRowStyles(\n  index: number,\n  total: number,\n  gap: GapProp,\n  styles: Styles,\n  align: 'start' | 'center' = 'start'\n): Style[] {\n  const row: Style[] = [align === 'center' ? styles.itemRowCenter : styles.itemRow];\n  if (index !== total - 1) row.push(getGapStyle(gap, styles));\n  return row;\n}\n\n/** Bullet dot marker — solid filled for level 0, outline ring for nested levels. */\nfunction dotMarker(level: number, styles: Styles): React.ReactElement {\n  return level === 0 ? (\n    <View style={styles.markerBulletWrap}>\n      <View style={styles.markerBulletDot} />\n    </View>\n  ) : (\n    <View style={styles.markerBulletSubWrap}>\n      <View style={styles.markerBulletSubDot} />\n    </View>\n  );\n}\n\nfunction renderBulletItem(\n  item: ListItem,\n  index: number,\n  total: number,\n  gap: GapProp,\n  styles: Styles,\n  level: number\n): React.ReactElement {\n  return (\n    <View key={index}>\n      <View style={buildRowStyles(index, total, gap, styles)}>\n        {dotMarker(level, styles)}\n        {/* flex:1 on the View, NOT on the Text — avoids Yoga height underestimation\n            for multi-line Text nodes that would cause overlapping rows. */}\n        <View style={styles.itemTextWrap}>\n          <PDFText style={styles.itemText}>{item.text}</PDFText>\n        </View>\n      </View>\n      {item.children && item.children.length > 0\n        ? renderItemList(item.children, 'bullet', gap, styles, level + 1)\n        : null}\n    </View>\n  );\n}\n\nfunction renderNumberedItem(\n  item: ListItem,\n  index: number,\n  total: number,\n  gap: GapProp,\n  styles: Styles\n): React.ReactElement {\n  return (\n    <View key={index} style={buildRowStyles(index, total, gap, styles, 'center')}>\n      <View style={styles.markerNumberBadge}>\n        <PDFText style={styles.markerNumberText}>{`${index + 1}`}</PDFText>\n      </View>\n      <View style={styles.itemTextWrap}>\n        <PDFText style={styles.itemText}>{item.text}</PDFText>\n      </View>\n    </View>\n  );\n}\n\nfunction renderChecklistItem(\n  item: ListItem,\n  index: number,\n  total: number,\n  gap: GapProp,\n  styles: Styles\n): React.ReactElement {\n  const isChecked = item.checked ?? true;\n  return (\n    <View key={index} style={buildRowStyles(index, total, gap, styles, 'center')}>\n      <View style={[styles.checkBox, isChecked ? styles.checkBoxChecked : {}]}>\n        {isChecked ? <PDFText style={styles.checkMark}>✓</PDFText> : null}\n      </View>\n      <View style={styles.itemTextWrap}>\n        <PDFText style={styles.itemText}>{item.text}</PDFText>\n      </View>\n    </View>\n  );\n}\n\nfunction renderIconItem(\n  item: ListItem,\n  index: number,\n  total: number,\n  gap: GapProp,\n  styles: Styles\n): React.ReactElement {\n  return (\n    <View key={index} style={buildRowStyles(index, total, gap, styles, 'center')}>\n      <View style={styles.iconBox}>\n        <PDFText style={styles.iconMark}>★</PDFText>\n      </View>\n      <View style={styles.itemTextWrap}>\n        <PDFText style={styles.itemText}>{item.text}</PDFText>\n      </View>\n    </View>\n  );\n}\n\nfunction renderMultiLevelItem(\n  item: ListItem,\n  index: number,\n  total: number,\n  gap: GapProp,\n  styles: Styles,\n  level: number\n): React.ReactElement {\n  return (\n    <View key={index}>\n      <View style={buildRowStyles(index, total, gap, styles)}>\n        {dotMarker(level, styles)}\n        <View style={styles.itemTextWrap}>\n          <PDFText\n            style={[\n              level === 0 ? styles.itemText : styles.itemTextSub,\n              level === 0 ? styles.itemTextBold : {},\n            ]}\n          >\n            {item.text}\n          </PDFText>\n        </View>\n      </View>\n      {item.children && item.children.length > 0\n        ? renderItemList(item.children, 'multi-level', gap, styles, level + 1)\n        : null}\n    </View>\n  );\n}\n\nfunction renderDescriptiveItem(\n  item: ListItem,\n  index: number,\n  total: number,\n  gap: GapProp,\n  styles: Styles\n): React.ReactElement {\n  return (\n    <View key={index} style={buildRowStyles(index, total, gap, styles)}>\n      <View style={styles.descriptiveAccent} />\n      <View style={styles.descriptiveContent}>\n        <PDFText style={styles.descriptiveTitle}>{item.text}</PDFText>\n        {item.description ? (\n          <PDFText style={styles.descriptiveDesc}>{item.description}</PDFText>\n        ) : null}\n      </View>\n    </View>\n  );\n}\n\nfunction renderItem(\n  item: ListItem,\n  index: number,\n  total: number,\n  variant: ListVariant,\n  gap: GapProp,\n  styles: Styles,\n  level: number\n): React.ReactElement | null {\n  switch (variant) {\n    case 'bullet':\n      return renderBulletItem(item, index, total, gap, styles, level);\n    case 'numbered':\n      return renderNumberedItem(item, index, total, gap, styles);\n    case 'checklist':\n      return renderChecklistItem(item, index, total, gap, styles);\n    case 'icon':\n      return renderIconItem(item, index, total, gap, styles);\n    case 'multi-level':\n      return renderMultiLevelItem(item, index, total, gap, styles, level);\n    case 'descriptive':\n      return renderDescriptiveItem(item, index, total, gap, styles);\n  }\n}\n\nfunction renderItemList(\n  items: ListItem[],\n  variant: ListVariant,\n  gap: GapProp,\n  styles: Styles,\n  level: number\n): React.ReactElement {\n  return (\n    <View style={level > 0 ? styles.childrenContainer : undefined}>\n      {items.map((item, index) =>\n        renderItem(item, index, items.length, variant, gap, styles, level)\n      )}\n    </View>\n  );\n}\n\nexport function PdfList({\n  items,\n  variant = 'bullet',\n  gap = 'sm',\n  style,\n  noWrap = false,\n  _level = 0,\n}: PdfListProps) {\n  const theme = usePdfxTheme();\n  const styles = useSafeMemo(() => createListStyles(theme), [theme]);\n\n  const containerStyles: Style[] = [styles.container];\n  if (_level > 0) containerStyles.push(styles.childrenContainer);\n  const styleArray = style ? [...containerStyles, style] : containerStyles;\n\n  return (\n    <View wrap={!noWrap} style={styleArray}>\n      {items.map((item, index) =>\n        renderItem(item, index, items.length, variant, gap, styles, _level)\n      )}\n    </View>\n  );\n}\n",
      "type": "registry:component"
    },
    {
      "path": "components/pdfx/list/pdfx-list.styles.ts",
      "content": "import { StyleSheet } from '@react-pdf/renderer';\nimport { usePdfxTheme } from '../lib/pdfx-theme-context';\ntype PdfxTheme = ReturnType<typeof usePdfxTheme>;\n\n/**\n * Creates all list styles derived from the active theme.\n * @param t - The resolved PdfxTheme instance.\n */\nexport function createListStyles(t: PdfxTheme) {\n  const { borderRadius, spacing, fontWeights, typography } = t.primitives;\n\n  return StyleSheet.create({\n    container: {\n      display: 'flex',\n      flexDirection: 'column',\n      width: '100%',\n      marginBottom: t.spacing.componentGap,\n    },\n    itemRow: {\n      flexDirection: 'row',\n      alignItems: 'flex-start',\n    },\n    itemRowCenter: {\n      flexDirection: 'row',\n      alignItems: 'center',\n    },\n    itemRowGapXs: { marginBottom: spacing[1] },\n    itemRowGapSm: { marginBottom: spacing[2] },\n    itemRowGapMd: { marginBottom: spacing[3] },\n    markerBulletWrap: {\n      width: spacing[4],\n      alignItems: 'center',\n      justifyContent: 'flex-start',\n      marginTop: spacing[1],\n    },\n    markerBulletDot: {\n      width: 5,\n      height: 5,\n      borderRadius: 3,\n      backgroundColor: t.colors.primary,\n    },\n    markerBulletSubWrap: {\n      width: spacing[4],\n      alignItems: 'center',\n      justifyContent: 'flex-start',\n      marginTop: spacing[1],\n    },\n    markerBulletSubDot: {\n      width: 4,\n      height: 4,\n      borderRadius: 2,\n      borderWidth: 1,\n      borderStyle: 'solid',\n      borderColor: t.colors.mutedForeground,\n      backgroundColor: 'transparent',\n    },\n    markerNumberBadge: {\n      width: spacing[5],\n      height: spacing[5],\n      borderRadius: spacing[5],\n      backgroundColor: t.colors.primary,\n      alignItems: 'center',\n      justifyContent: 'center',\n      marginRight: spacing[2],\n    },\n    markerNumberText: {\n      fontFamily: t.typography.body.fontFamily,\n      fontSize: typography.xs,\n      color: t.colors.primaryForeground,\n      fontWeight: fontWeights.bold,\n    },\n    checkBox: {\n      width: spacing[4],\n      height: spacing[4],\n      borderWidth: 1.5,\n      borderColor: t.colors.border,\n      borderStyle: 'solid',\n      borderRadius: 3,\n      marginRight: spacing[2],\n      alignItems: 'center',\n      justifyContent: 'center',\n      backgroundColor: t.colors.background,\n    },\n    checkBoxChecked: {\n      backgroundColor: t.colors.success,\n      borderColor: t.colors.success,\n    },\n    checkMark: {\n      fontFamily: t.typography.body.fontFamily,\n      fontSize: 8,\n      color: t.colors.background,\n      fontWeight: fontWeights.bold,\n    },\n    iconBox: {\n      width: spacing[5],\n      height: spacing[5],\n      borderRadius: borderRadius.md,\n      backgroundColor: t.colors.primary,\n      alignItems: 'center',\n      justifyContent: 'center',\n      marginRight: spacing[2],\n    },\n    iconMark: {\n      fontFamily: t.typography.body.fontFamily,\n      fontSize: 9,\n      color: t.colors.primaryForeground,\n      fontWeight: fontWeights.bold,\n    },\n    itemText: {\n      // No flex here — Text nodes with flex shorthand get flexBasis:0, which causes\n      // Yoga to under-measure multi-line text height → overlapping rows.\n      // flex:1 must live on the wrapping View, not on the Text itself.\n      fontFamily: t.typography.body.fontFamily,\n      fontSize: t.typography.body.fontSize,\n      lineHeight: t.typography.body.lineHeight,\n      color: t.colors.foreground,\n    },\n    itemTextSub: {\n      // Same: no flex — see itemText comment.\n      fontFamily: t.typography.body.fontFamily,\n      fontSize: t.typography.body.fontSize - 0.5,\n      lineHeight: t.typography.body.lineHeight,\n      color: t.colors.mutedForeground,\n    },\n    itemTextWrap: {\n      // Companion to itemText / itemTextSub. Apply this to the View that wraps\n      // the Text so flex expansion happens at the View level, not the Text level.\n      flex: 1,\n    },\n    itemTextBold: {\n      fontWeight: fontWeights.semibold,\n    },\n    descriptiveTitle: {\n      fontFamily: t.typography.body.fontFamily,\n      fontSize: t.typography.body.fontSize,\n      lineHeight: t.typography.body.lineHeight,\n      color: t.colors.foreground,\n      fontWeight: fontWeights.semibold,\n    },\n    descriptiveDesc: {\n      fontFamily: t.typography.body.fontFamily,\n      fontSize: typography.sm,\n      lineHeight: t.typography.body.lineHeight,\n      color: t.colors.mutedForeground,\n      marginTop: 1,\n    },\n    descriptiveAccent: {\n      width: 3,\n      borderRadius: borderRadius.sm,\n      backgroundColor: t.colors.primary,\n      marginRight: spacing[3],\n      minHeight: spacing[4],\n    },\n    descriptiveContent: {\n      flex: 1,\n    },\n    childrenContainer: {\n      marginLeft: spacing[5],\n      marginTop: spacing[1],\n      display: 'flex',\n      flexDirection: 'column',\n    },\n  });\n}\n",
      "type": "registry:component"
    },
    {
      "path": "components/pdfx/list/pdfx-list.types.ts",
      "content": "import type { Style } from '@react-pdf/types';\n\n/** List visual style variant. */\nexport type ListVariant =\n  | 'bullet'\n  | 'numbered'\n  | 'checklist'\n  | 'icon'\n  | 'multi-level'\n  | 'descriptive';\n\n/**\n * A single list item, optionally with nested children.\n * Props - `text` | `description` | `checked` | `children`\n * @see {@link ListItem}\n */\nexport interface ListItem {\n  text: string;\n  description?: string;\n  checked?: boolean;\n  children?: ListItem[];\n}\n\n/**\n * List of items with multiple style variants including bullet, numbered, checklist, and descriptive.\n * Props - `items` | `variant` | `gap` | `style` | `_level` | `noWrap`\n * @see {@link PdfListProps}\n */\nexport interface PdfListProps {\n  items: ListItem[];\n  /**\n   * @default 'bullet'\n   */\n  variant?: ListVariant;\n  /**\n   * @default 'sm'\n   */\n  gap?: 'xs' | 'sm' | 'md';\n  style?: Style;\n  _level?: number;\n  /**\n   * @default false\n   */\n  noWrap?: boolean;\n}\n",
      "type": "registry:component"
    }
  ],
  "dependencies": [
    "@react-pdf/renderer"
  ],
  "registryDependencies": [
    "theme"
  ]
}