{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "environment-variables",
  "type": "registry:component",
  "title": "EnvironmentVariables",
  "description": "Composable environment variable editor with .env upload, bulk paste, reveal controls, and submit helpers.",
  "dependencies": [
    "lucide-react@^1.31.0"
  ],
  "registryDependencies": [
    "https://control-ui.dev/r/button.json",
    "https://control-ui.dev/r/core.json",
    "https://control-ui.dev/r/input.json",
    "https://control-ui.dev/r/input-group.json"
  ],
  "files": [
    {
      "path": "src/registry/hooks/use-environment-variables.ts",
      "target": "@components/control-ui/hooks/use-environment-variables.ts",
      "type": "registry:hook",
      "content": "\"use client\";\n\nimport { useRef, useState } from \"react\";\nimport type { EnvironmentVariableEntry } from \"@/components/control-ui/lib/env-file\";\nimport {\n  collectEnvironmentVariables,\n  createEmptyEnvironmentVariableEntry,\n  getDuplicateEnvironmentVariableKeys,\n  parseEnvFileText,\n  readEnvFile,\n  rowsToEnvFileText,\n} from \"@/components/control-ui/lib/env-file\";\n\nexport type EnvironmentVariableRow = EnvironmentVariableEntry;\n\nexport type EnvironmentVariablesImportMode = \"auto\" | \"bulk\";\n\nexport interface UseEnvironmentVariablesOptions<TRow extends EnvironmentVariableRow = EnvironmentVariableRow> {\n  initialRows?: readonly TRow[];\n  rows?: readonly TRow[];\n  onRowsChange?: (rows: TRow[]) => void;\n  createDefaultRow?: () => TRow;\n  createRow?: (entry: EnvironmentVariableEntry) => TRow;\n  getEditableRows?: (rows: readonly TRow[]) => readonly TRow[];\n  getPreservedRows?: (rows: readonly TRow[]) => readonly TRow[];\n  maxUploadSize?: number;\n}\n\nexport interface EnvironmentVariablesFileUploadEvent {\n  target: {\n    files?: FileList | readonly File[] | null;\n    value: string;\n  };\n}\n\nexport interface EnvironmentVariablesImportOptions {\n  targetIndex?: number;\n  mode?: EnvironmentVariablesImportMode;\n}\n\nexport interface EnvironmentVariablesController<TRow extends EnvironmentVariableRow = EnvironmentVariableRow> {\n  rows: readonly TRow[];\n  uploadError: string | null;\n  duplicateKeys: Set<string>;\n  hasDuplicateKeys: boolean;\n  isDirty: boolean;\n  setRows: (rows: readonly TRow[]) => TRow[];\n  resetRows: (rows?: readonly TRow[]) => TRow[];\n  appendRow: (row?: TRow) => TRow[];\n  updateRow: (index: number, patch: Partial<EnvironmentVariableEntry>) => TRow[];\n  removeRow: (index: number) => TRow[];\n  getRowsForSubmit: () => TRow[];\n  getEnvironmentVariablesForSubmit: () => Record<string, string>;\n  getEnvFileTextForSubmit: () => string;\n  importText: (text: string, options?: EnvironmentVariablesImportOptions) => boolean;\n  handleFileUpload: (event: EnvironmentVariablesFileUploadEvent) => Promise<void>;\n  handlePaste: (index: number, text: string) => boolean;\n  clearUploadError: () => void;\n  getRowId: (index: number) => string;\n  isValueRevealed: (index: number) => boolean;\n  toggleValueVisibility: (index: number) => void;\n  rowHasDuplicateKey: (index: number) => boolean;\n}\n\nfunction defaultCreateRow(entry: EnvironmentVariableEntry): EnvironmentVariableRow {\n  return { key: entry.key, value: entry.value };\n}\n\nfunction defaultEditableRows<TRow extends EnvironmentVariableRow>(rows: readonly TRow[]) {\n  return rows;\n}\n\nfunction defaultPreservedRows() {\n  return [];\n}\n\nfunction normalizeRows<TRow extends EnvironmentVariableRow>(rows: readonly TRow[] | undefined, createDefaultRow: () => TRow) {\n  return rows && rows.length > 0 ? rows.map((row) => ({ ...row })) : [createDefaultRow()];\n}\n\nfunction hasOnlyEmptyRow(rows: readonly EnvironmentVariableEntry[]) {\n  if (rows.length !== 1) return false;\n  const [firstRow] = rows;\n  if (!firstRow) return false;\n  return !firstRow.key.trim() && !firstRow.value;\n}\n\nfunction areRowsEqual(a: readonly EnvironmentVariableEntry[], b: readonly EnvironmentVariableEntry[]) {\n  if (a.length !== b.length) return false;\n\n  for (let index = 0; index < a.length; index++) {\n    const aRow = a[index];\n    const bRow = b[index];\n    if (!aRow || !bRow || aRow.key !== bRow.key || aRow.value !== bRow.value) {\n      return false;\n    }\n  }\n\n  return true;\n}\n\n// implementation works on plain entries so default row factories type-check without casts\nexport function useEnvironmentVariables<TRow extends EnvironmentVariableRow = EnvironmentVariableRow>(\n  options?: UseEnvironmentVariablesOptions<TRow>,\n): EnvironmentVariablesController<TRow>;\nexport function useEnvironmentVariables({\n  initialRows,\n  rows: controlledRows,\n  onRowsChange,\n  createDefaultRow = createEmptyEnvironmentVariableEntry,\n  createRow = defaultCreateRow,\n  getEditableRows = defaultEditableRows,\n  getPreservedRows = defaultPreservedRows,\n  maxUploadSize,\n}: UseEnvironmentVariablesOptions<EnvironmentVariableRow> = {}): EnvironmentVariablesController<EnvironmentVariableRow> {\n  const initialSourceRows = controlledRows ?? initialRows;\n  const nextRowId = useRef(0);\n  const fallbackRowIds = useRef<Record<number, string>>({});\n  const [uncontrolledRows, setUncontrolledRows] = useState(() => normalizeRows(initialSourceRows, createDefaultRow));\n  const [baselineRows, setBaselineRows] = useState(() => normalizeRows(initialSourceRows, createDefaultRow));\n  const [rowIds, setRowIds] = useState(() => normalizeRows(initialSourceRows, createDefaultRow).map(() => createRowId()));\n  const [uploadError, setUploadError] = useState<string | null>(null);\n  const [revealedValues, setRevealedValues] = useState<Record<number, boolean>>({});\n\n  const rows = controlledRows ? normalizeRows(controlledRows, createDefaultRow) : uncontrolledRows;\n  const editableRows = (sourceRows: readonly EnvironmentVariableRow[] = rows) => getEditableRows(sourceRows);\n  const preservedRows = (sourceRows: readonly EnvironmentVariableRow[] = rows) => getPreservedRows(sourceRows);\n\n  function createRowId() {\n    const rowId = nextRowId.current;\n    nextRowId.current += 1;\n    return `environment-variable-row-${rowId}`;\n  }\n\n  function commitRows(nextRows: readonly EnvironmentVariableRow[], nextRowIds?: readonly string[]) {\n    const normalizedRows = normalizeRows(nextRows, createDefaultRow);\n    const normalizedRowIds = normalizedRows.map((_, index) => nextRowIds?.[index] ?? rowIds[index] ?? createRowId());\n\n    if (!controlledRows) {\n      setUncontrolledRows(normalizedRows);\n    }\n\n    setRowIds(normalizedRowIds);\n    onRowsChange?.(normalizedRows);\n    return normalizedRows;\n  }\n\n  function setRows(nextRows: readonly EnvironmentVariableRow[]) {\n    return commitRows(nextRows);\n  }\n\n  function resetRows(nextRows: readonly EnvironmentVariableRow[] = baselineRows) {\n    const normalizedRows = normalizeRows(nextRows, createDefaultRow);\n    commitRows(\n      normalizedRows,\n      normalizedRows.map(() => createRowId()),\n    );\n    setBaselineRows(normalizedRows);\n    setRevealedValues({});\n    setUploadError(null);\n    return normalizedRows;\n  }\n\n  function getRowsForSubmit() {\n    return [...rows];\n  }\n\n  function appendRow(row = createDefaultRow()) {\n    return commitRows([...rows, row], [...rowIds, createRowId()]);\n  }\n\n  function updateRow(index: number, patch: Partial<EnvironmentVariableEntry>) {\n    return commitRows(\n      rows.map((row, rowIndex) => (rowIndex === index ? { ...row, ...patch } : row)),\n      rowIds,\n    );\n  }\n\n  function removeRow(index: number) {\n    const nextRows = rows.filter((_, rowIndex) => rowIndex !== index);\n    const nextRowIds = rowIds.filter((_, rowIndex) => rowIndex !== index);\n    setRevealedValues({});\n    return commitRows(nextRows.length > 0 ? nextRows : [createDefaultRow()], nextRows.length > 0 ? nextRowIds : [createRowId()]);\n  }\n\n  function mergeParsedRows(parsedRows: readonly EnvironmentVariableEntry[], targetIndex?: number) {\n    const newRows = parsedRows.map(createRow);\n    const newRowIds = newRows.map(() => createRowId());\n    const currentEditableRows = editableRows(rows);\n\n    if (hasOnlyEmptyRow(currentEditableRows)) {\n      const preserved = preservedRows(rows);\n      return commitRows([...preserved, ...newRows], [...rowIds.slice(0, preserved.length), ...newRowIds]);\n    }\n\n    if (typeof targetIndex !== \"number\") {\n      return commitRows([...rows, ...newRows], [...rowIds, ...newRowIds]);\n    }\n\n    const targetRow = rows[targetIndex];\n    if (!targetRow) {\n      return commitRows([...rows, ...newRows], [...rowIds, ...newRowIds]);\n    }\n\n    const targetIsEmpty = !targetRow.key.trim() && !targetRow.value;\n    const before = rows.slice(0, targetIndex + (targetIsEmpty ? 0 : 1));\n    const after = rows.slice(targetIndex + 1);\n    const beforeIds = rowIds.slice(0, targetIndex + (targetIsEmpty ? 0 : 1));\n    const afterIds = rowIds.slice(targetIndex + 1);\n    return commitRows([...before, ...newRows, ...after], [...beforeIds, ...newRowIds, ...afterIds]);\n  }\n\n  function importText(text: string, options: EnvironmentVariablesImportOptions = {}) {\n    const entries = parseEnvironmentVariablesImportText(text, options.mode ?? \"bulk\");\n    if (entries.length === 0) return false;\n\n    mergeParsedRows(entries, options.targetIndex);\n    setRevealedValues({});\n    setUploadError(null);\n    return true;\n  }\n\n  function handlePaste(index: number, text: string) {\n    return importText(text, { targetIndex: index, mode: \"auto\" });\n  }\n\n  async function handleFileUpload(event: EnvironmentVariablesFileUploadEvent) {\n    setUploadError(null);\n    const file = event.target.files?.[0];\n    if (!file) return;\n\n    const result = await readEnvFile(file, { maxSize: maxUploadSize });\n    if (!result.ok) {\n      setUploadError(result.error);\n      event.target.value = \"\";\n      return;\n    }\n\n    mergeParsedRows(result.entries);\n    setRevealedValues({});\n\n    event.target.value = \"\";\n  }\n\n  function getRowId(index: number) {\n    const rowId = rowIds[index] ?? fallbackRowIds.current[index];\n    if (rowId) return rowId;\n\n    const fallbackRowId = createRowId();\n    fallbackRowIds.current[index] = fallbackRowId;\n    return fallbackRowId;\n  }\n\n  const duplicateKeys = getDuplicateEnvironmentVariableKeys(rows);\n  const rowsDirty = !areRowsEqual(rows, baselineRows);\n\n  return {\n    rows,\n    uploadError,\n    duplicateKeys,\n    hasDuplicateKeys: duplicateKeys.size > 0,\n    isDirty: rowsDirty,\n    setRows,\n    resetRows,\n    appendRow,\n    updateRow,\n    removeRow,\n    getRowsForSubmit,\n    getEnvironmentVariablesForSubmit: () => collectEnvironmentVariables(getRowsForSubmit()),\n    getEnvFileTextForSubmit: () => rowsToEnvFileText(getRowsForSubmit()),\n    importText,\n    handleFileUpload,\n    handlePaste,\n    clearUploadError: () => setUploadError(null),\n    getRowId,\n    isValueRevealed: (index) => Boolean(revealedValues[index]),\n    toggleValueVisibility: (index) => setRevealedValues((previous) => ({ ...previous, [index]: !previous[index] })),\n    rowHasDuplicateKey: (index) => duplicateKeys.has(rows[index]?.key.trim() ?? \"\"),\n  };\n}\n\nexport function parseEnvironmentVariablesImportText(text: string, mode: EnvironmentVariablesImportMode = \"bulk\") {\n  const trimmedText = text.trim();\n  if (!trimmedText.includes(\"=\")) return [];\n\n  const entries = parseEnvFileText(trimmedText);\n  if (entries.length === 0) return [];\n  if (mode === \"bulk\") return entries;\n\n  const firstAssignmentLooksLikeEnvVar = /^(?:export\\s+)?[A-Z_][A-Z0-9_]*\\s*=/.test(trimmedText);\n  const hasBulkShape = entries.length > 1 || trimmedText.includes(\"\\n\") || firstAssignmentLooksLikeEnvVar;\n\n  return hasBulkShape ? entries : [];\n}\n"
    },
    {
      "path": "src/registry/knob-contracts/environment-variables-knobs.ts",
      "target": "@components/control-ui/knob-contracts/environment-variables-knobs.ts",
      "type": "registry:component",
      "content": "// Generated from src/registry/sources/control-ui/recipes/environment-variables.css by scripts/gen-knob-contracts.ts — run `bun run sync:knobs`.\nexport const environmentVariablesKnobs = [\n  \"--cui-environment-variables-title-foreground\",\n  \"--cui-environment-variables-meta-foreground\",\n  \"--cui-environment-variables-error-foreground\",\n  \"--cui-environment-variables-message-background\",\n  \"--cui-environment-variables-message-foreground\",\n  \"--cui-environment-variables-message-border-color\",\n] as const;\nexport type EnvironmentVariablesKnobStyle = Partial<Record<(typeof environmentVariablesKnobs)[number], string>>;\n"
    },
    {
      "path": "src/registry/lib/env-file.ts",
      "target": "@components/control-ui/lib/env-file.ts",
      "type": "registry:lib",
      "content": "export interface EnvironmentVariableEntry {\n  key: string;\n  value: string;\n}\n\nexport const ENV_FILE_MAX_SIZE = 64 * 1024;\nexport const DUPLICATE_ENVIRONMENT_VARIABLE_MESSAGE = \"Environment variable keys must be unique\";\n\nexport class DuplicateEnvironmentVariableKeyError extends Error {\n  constructor(public readonly key: string) {\n    super(DUPLICATE_ENVIRONMENT_VARIABLE_MESSAGE);\n    this.name = \"DuplicateEnvironmentVariableKeyError\";\n  }\n}\n\nexport function createEmptyEnvironmentVariableEntry(): EnvironmentVariableEntry {\n  return { key: \"\", value: \"\" };\n}\n\nexport function rowsFromEnvironmentVariables(envVars: Record<string, unknown> | undefined): EnvironmentVariableEntry[] {\n  const entries = Object.entries(envVars ?? {});\n  return entries.length > 0 ? entries.map(([key, value]) => ({ key, value: String(value) })) : [createEmptyEnvironmentVariableEntry()];\n}\n\nexport function getDuplicateEnvironmentVariableKeys(rows: readonly EnvironmentVariableEntry[]): Set<string> {\n  const seen = new Set<string>();\n  const duplicates = new Set<string>();\n\n  for (const row of rows) {\n    const key = row.key.trim();\n    if (!key) continue;\n\n    if (seen.has(key)) {\n      duplicates.add(key);\n    } else {\n      seen.add(key);\n    }\n  }\n\n  return duplicates;\n}\n\nexport function collectEnvironmentVariables(rows: readonly EnvironmentVariableEntry[]): Record<string, string> {\n  const envVars: Record<string, string> = {};\n  const seen = new Set<string>();\n\n  for (const row of rows) {\n    const key = row.key.trim();\n    if (!key) continue;\n\n    if (seen.has(key)) {\n      throw new DuplicateEnvironmentVariableKeyError(key);\n    }\n\n    seen.add(key);\n    envVars[key] = row.value;\n  }\n\n  return envVars;\n}\n\nfunction parseEnvAssignment(line: string): { key: string; rawValue: string } | undefined {\n  const normalizedLine = line.trim();\n  if (!normalizedLine || normalizedLine.startsWith(\"#\")) return undefined;\n\n  const stripped = normalizedLine.replace(/^export\\s+/, \"\");\n  const equalsIndex = stripped.indexOf(\"=\");\n  if (equalsIndex === -1) return undefined;\n\n  const key = stripped.slice(0, equalsIndex).trim();\n  if (!key) return undefined;\n  return { key, rawValue: stripped.slice(equalsIndex + 1) };\n}\n\nfunction openingQuote(value: string): '\"' | \"'\" | undefined {\n  if (value.startsWith('\"')) return '\"';\n  if (value.startsWith(\"'\")) return \"'\";\n  return undefined;\n}\n\nfunction parseUnquotedEnvValue(rawValue: string): string {\n  const commentIndex = rawValue.indexOf(\" #\");\n  return (commentIndex === -1 ? rawValue : rawValue.slice(0, commentIndex)).trim();\n}\n\nfunction parseQuotedEnvValue(lines: string[], lineIndex: number, value: string, quote: '\"' | \"'\") {\n  const closingIndex = findClosingQuoteIndex(value, quote, 1);\n  if (closingIndex !== -1) {\n    return { value: unescapeQuotedValue(value.slice(1, closingIndex), quote), nextLineIndex: lineIndex + 1 };\n  }\n\n  const parts = [value.slice(1)];\n  let nextLineIndex = lineIndex + 1;\n  while (nextLineIndex < lines.length) {\n    const nextLine = lines[nextLineIndex];\n    if (nextLine === undefined) break;\n    const endIndex = findClosingQuoteIndex(nextLine, quote);\n    if (endIndex !== -1) {\n      parts.push(nextLine.slice(0, endIndex));\n      nextLineIndex += 1;\n      break;\n    }\n    parts.push(nextLine);\n    nextLineIndex += 1;\n  }\n\n  return { value: unescapeQuotedValue(parts.join(\"\\n\"), quote), nextLineIndex };\n}\n\nfunction parseEnvValue(lines: string[], lineIndex: number, rawValue: string) {\n  const value = rawValue.trimStart();\n  const quote = openingQuote(value);\n  if (quote) return parseQuotedEnvValue(lines, lineIndex, value, quote);\n  return { value: parseUnquotedEnvValue(rawValue), nextLineIndex: lineIndex + 1 };\n}\n\nexport function parseEnvFileText(text: string): EnvironmentVariableEntry[] {\n  const results: EnvironmentVariableEntry[] = [];\n  const lines = text.replace(/^\\uFEFF/, \"\").split(/\\r?\\n/);\n  let lineIndex = 0;\n\n  while (lineIndex < lines.length) {\n    const currentLine = lines[lineIndex];\n    if (currentLine === undefined) break;\n    const assignment = parseEnvAssignment(currentLine);\n    if (!assignment) {\n      lineIndex++;\n      continue;\n    }\n\n    const parsedValue = parseEnvValue(lines, lineIndex, assignment.rawValue);\n    results.push({ key: assignment.key, value: parsedValue.value });\n    lineIndex = parsedValue.nextLineIndex;\n  }\n\n  return results;\n}\n\nfunction findClosingQuoteIndex(value: string, quote: '\"' | \"'\", startIndex = 0): number {\n  for (let index = startIndex; index < value.length; index++) {\n    if (value[index] === quote && !isEscaped(value, index)) {\n      return index;\n    }\n  }\n  return -1;\n}\n\nfunction isEscaped(value: string, index: number): boolean {\n  let slashCount = 0;\n  for (let cursor = index - 1; cursor >= 0 && value[cursor] === \"\\\\\"; cursor--) {\n    slashCount++;\n  }\n  return slashCount % 2 === 1;\n}\n\nconst DOUBLE_QUOTED_CONTROL_ESCAPES: Readonly<Record<string, string | undefined>> = {\n  n: \"\\n\",\n  r: \"\\r\",\n  t: \"\\t\",\n};\n\nfunction escapedQuotedCharacter(next: string, quote: '\"' | \"'\"): string | undefined {\n  if (next === quote || next === \"\\\\\") return next;\n  if (quote === \"'\") return undefined;\n  return DOUBLE_QUOTED_CONTROL_ESCAPES[next];\n}\n\nfunction unescapeQuotedValue(value: string, quote: '\"' | \"'\"): string {\n  let result = \"\";\n\n  for (let index = 0; index < value.length; index++) {\n    const current = value[index];\n    const next = value[index + 1];\n\n    if (current !== \"\\\\\" || next === undefined) {\n      result += current;\n      continue;\n    }\n\n    const unescaped = escapedQuotedCharacter(next, quote);\n    result += unescaped ?? current;\n    if (unescaped !== undefined) index++;\n  }\n\n  return result;\n}\n\nfunction escapeDoubleQuotedValue(value: string): string {\n  return value.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"');\n}\n\nfunction shouldQuoteValue(value: string) {\n  return value.includes(\"\\n\") || value.includes(\"\\r\") || value.includes(\" #\") || /^\\s|\\s$/.test(value);\n}\n\nexport function rowsToEnvFileText(rows: readonly EnvironmentVariableEntry[]): string {\n  const lines: string[] = [];\n\n  for (const row of rows) {\n    const key = row.key.trim();\n    if (!key) continue;\n\n    const value = row.value;\n    lines.push(shouldQuoteValue(value) ? `${key}=\"${escapeDoubleQuotedValue(value)}\"` : `${key}=${value}`);\n  }\n\n  return lines.join(\"\\n\");\n}\n\nexport async function readEnvFile(\n  file: File,\n  options: { maxSize?: number } = {},\n): Promise<{ ok: true; entries: EnvironmentVariableEntry[] } | { ok: false; error: string }> {\n  const maxSize = options.maxSize ?? ENV_FILE_MAX_SIZE;\n\n  if (file.size > maxSize) {\n    return { ok: false, error: `File is too large (max ${Math.ceil(maxSize / 1024)} KB).` };\n  }\n\n  let text: string;\n  try {\n    text = await readFileText(file);\n  } catch {\n    return { ok: false, error: \"Could not read the selected file. Please try again.\" };\n  }\n\n  if (text.includes(\"\\0\")) {\n    return { ok: false, error: \"File appears to be binary. Please import a plain-text .env file.\" };\n  }\n\n  const entries = parseEnvFileText(text);\n  if (entries.length === 0) {\n    return { ok: false, error: \"No valid environment variables found in the file.\" };\n  }\n\n  return { ok: true, entries };\n}\n\nasync function readFileText(file: File): Promise<string> {\n  if (typeof file.text === \"function\") {\n    try {\n      return await file.text();\n    } catch {\n      // Some test DOMs expose Blob.text without implementing every Blob source.\n    }\n  }\n\n  return new Promise((resolve, reject) => {\n    const reader = new FileReader();\n    reader.onload = () => {\n      if (typeof reader.result === \"string\") {\n        resolve(reader.result);\n      } else {\n        reject(new Error(\"FileReader returned non-text content.\"));\n      }\n    };\n    reader.onerror = () => reject(reader.error ?? new Error(\"FileReader failed.\"));\n    reader.readAsText(file);\n  });\n}\n"
    },
    {
      "path": "src/registry/sources/control-ui/environment-variables.tsx",
      "target": "@components/control-ui/environment-variables.tsx",
      "type": "registry:component",
      "content": "\"use client\";\n\nimport { AlertTriangleIcon, EyeIcon, EyeOffIcon, FileUpIcon, PlusIcon, RotateCcwIcon, SaveIcon, Trash2Icon } from \"lucide-react\";\nimport type { ChangeEvent, ClipboardEvent, ComponentProps, CSSProperties, ReactNode } from \"react\";\nimport { createContext, use, useId } from \"react\";\nimport type { FormSubmitEvent } from \"@/components/control-ui/control-props\";\nimport {\n  type EnvironmentVariableRow,\n  type EnvironmentVariablesController,\n  type UseEnvironmentVariablesOptions,\n  useEnvironmentVariables,\n} from \"@/components/control-ui/hooks/use-environment-variables\";\nimport type { EnvironmentVariablesKnobStyle } from \"@/components/control-ui/knob-contracts/environment-variables-knobs\";\nimport { cn } from \"@/components/control-ui/lib/cn\";\nimport {\n  collectEnvironmentVariables,\n  DUPLICATE_ENVIRONMENT_VARIABLE_MESSAGE,\n  rowsToEnvFileText,\n} from \"@/components/control-ui/lib/env-file\";\nimport { Button } from \"@/components/control-ui/ui/button\";\nimport { Input } from \"@/components/control-ui/ui/input\";\nimport { InputGroup, InputGroupAddon, InputGroupInput } from \"@/components/control-ui/ui/input-group\";\n\nexport type EnvironmentVariablesRowErrors = Record<number, { key?: ReactNode; value?: ReactNode }>;\n\nexport type EnvironmentVariablesSubmitPayload<TRow extends EnvironmentVariableRow = EnvironmentVariableRow> = {\n  rows: TRow[];\n  variables: Record<string, string>;\n  envFileText: string;\n  reset: () => void;\n};\n\n/**\n * React context cannot carry Root's TRow, so controller is projected onto row-agnostic surface here.\n * TRow stays exact on Root and onSubmit.\n */\ntype EnvironmentVariablesRenderController = Omit<EnvironmentVariablesController, \"setRows\" | \"resetRows\" | \"appendRow\"> & {\n  resetRows: () => void;\n  appendRow: () => void;\n};\n\ntype EnvironmentVariablesContextValue = {\n  editor: EnvironmentVariablesRenderController;\n  disabled: boolean;\n  readOnly: boolean;\n  rowErrors?: EnvironmentVariablesRowErrors;\n  keyLabel: ReactNode;\n  valueLabel: ReactNode;\n  keyPlaceholder: string;\n  valuePlaceholder: string;\n  duplicateKeyMessage: ReactNode;\n};\n\nconst EnvironmentVariablesContext = createContext<EnvironmentVariablesContextValue | null>(null);\n\nfunction useEnvironmentVariablesContext(componentName: string) {\n  const context = use(EnvironmentVariablesContext);\n  if (!context) throw new Error(`${componentName} must be used inside <EnvironmentVariables.Root>.`);\n  return context;\n}\n\nexport type EnvironmentVariablesRootProps<TRow extends EnvironmentVariableRow = EnvironmentVariableRow> = Omit<\n  ComponentProps<\"form\">,\n  \"onSubmit\"\n> & {\n  editor: EnvironmentVariablesController<TRow>;\n  disabled?: boolean;\n  readOnly?: boolean;\n  rowErrors?: EnvironmentVariablesRowErrors;\n  keyLabel?: ReactNode;\n  valueLabel?: ReactNode;\n  keyPlaceholder?: string;\n  valuePlaceholder?: string;\n  duplicateKeyMessage?: ReactNode;\n  onSubmit?: (payload: EnvironmentVariablesSubmitPayload<TRow>) => void | Promise<void>;\n} & { style?: CSSProperties & EnvironmentVariablesKnobStyle };\n\nexport function EnvironmentVariablesRoot<TRow extends EnvironmentVariableRow = EnvironmentVariableRow>({\n  editor,\n  disabled = false,\n  readOnly = false,\n  rowErrors,\n  keyLabel = \"Key\",\n  valueLabel = \"Value\",\n  keyPlaceholder = \"OPENAI_API_KEY\",\n  valuePlaceholder = \"sk-...\",\n  duplicateKeyMessage = DUPLICATE_ENVIRONMENT_VARIABLE_MESSAGE,\n  onSubmit,\n  className,\n  children,\n  ...props\n}: EnvironmentVariablesRootProps<TRow>) {\n  function handleSubmit(event: FormSubmitEvent) {\n    if (event.defaultPrevented || !onSubmit) return;\n\n    event.preventDefault();\n    if (editor.hasDuplicateKeys) return;\n\n    const rows = editor.getRowsForSubmit();\n    const payload = {\n      rows,\n      variables: collectEnvironmentVariables(rows),\n      envFileText: rowsToEnvFileText(rows),\n      reset: () => editor.resetRows(rows),\n    } satisfies EnvironmentVariablesSubmitPayload<TRow>;\n\n    void onSubmit(payload);\n  }\n\n  return (\n    <EnvironmentVariablesContext.Provider\n      value={{\n        editor,\n        disabled,\n        readOnly,\n        rowErrors,\n        keyLabel,\n        valueLabel,\n        keyPlaceholder,\n        valuePlaceholder,\n        duplicateKeyMessage,\n      }}\n    >\n      <form\n        data-control-ui=\"environment-variables\"\n        data-control-family=\"environment-variables\"\n        data-slot=\"root\"\n        data-surface=\"panel\"\n        data-disabled={disabled ? \"true\" : undefined}\n        data-readonly={readOnly ? \"true\" : undefined}\n        className={cn(\"flex min-w-0 flex-col gap-4\", className)}\n        onSubmit={handleSubmit}\n        {...props}\n      >\n        {children}\n      </form>\n    </EnvironmentVariablesContext.Provider>\n  );\n}\n\nexport type EnvironmentVariablesProps<TRow extends EnvironmentVariableRow = EnvironmentVariableRow> = Omit<\n  EnvironmentVariablesRootProps<TRow>,\n  \"editor\"\n> &\n  UseEnvironmentVariablesOptions<TRow> & {\n    editor?: EnvironmentVariablesController<TRow>;\n    title?: ReactNode;\n    description?: ReactNode;\n    error?: ReactNode;\n    submitLabel?: ReactNode;\n    resetLabel?: ReactNode;\n    addLabel?: ReactNode;\n    hideDefaultActions?: boolean;\n  };\n\nfunction EnvironmentVariablesComponent<TRow extends EnvironmentVariableRow = EnvironmentVariableRow>({\n  editor: providedEditor,\n  initialRows,\n  rows,\n  onRowsChange,\n  createDefaultRow,\n  createRow,\n  getEditableRows,\n  getPreservedRows,\n  maxUploadSize,\n  title = \"Environment variables\",\n  description,\n  error,\n  submitLabel = \"Save\",\n  resetLabel = \"Reset\",\n  addLabel = \"Add variable\",\n  hideDefaultActions = false,\n  children,\n  onSubmit,\n  ...props\n}: EnvironmentVariablesProps<TRow>) {\n  const internalEditor = useEnvironmentVariables({\n    initialRows,\n    rows,\n    onRowsChange,\n    createDefaultRow,\n    createRow,\n    getEditableRows,\n    getPreservedRows,\n    maxUploadSize,\n  });\n  const editor = providedEditor ?? internalEditor;\n\n  return (\n    <EnvironmentVariablesRoot editor={editor} onSubmit={onSubmit} {...props}>\n      {children ?? (\n        <>\n          <EnvironmentVariablesHeader title={title} description={description} />\n          <EnvironmentVariablesToolbar />\n          <EnvironmentVariablesUploadError />\n          <EnvironmentVariablesRows />\n          <EnvironmentVariablesDuplicateKeysError />\n          <EnvironmentVariablesMessage error={error} />\n          {!hideDefaultActions && (\n            <EnvironmentVariablesActions>\n              <EnvironmentVariablesAddButton>{addLabel}</EnvironmentVariablesAddButton>\n              <div className=\"flex min-w-0 items-center gap-2\">\n                <EnvironmentVariablesResetButton>{resetLabel}</EnvironmentVariablesResetButton>\n                {onSubmit ? <EnvironmentVariablesSubmitButton>{submitLabel}</EnvironmentVariablesSubmitButton> : null}\n              </div>\n            </EnvironmentVariablesActions>\n          )}\n        </>\n      )}\n    </EnvironmentVariablesRoot>\n  );\n}\n\nexport type EnvironmentVariablesHeaderProps = ComponentProps<\"div\"> & {\n  title?: ReactNode;\n  description?: ReactNode;\n} & { style?: CSSProperties & EnvironmentVariablesKnobStyle };\n\nexport function EnvironmentVariablesHeader({ title, description, className, children, ...props }: EnvironmentVariablesHeaderProps) {\n  if (children) {\n    return (\n      <div\n        data-control-ui=\"environment-variables\"\n        data-control-family=\"environment-variables\"\n        data-slot=\"header\"\n        className={cn(\"flex flex-col gap-1\", className)}\n        {...props}\n      >\n        {children}\n      </div>\n    );\n  }\n\n  if (!title && !description) return null;\n\n  return (\n    <div\n      data-control-ui=\"environment-variables\"\n      data-control-family=\"environment-variables\"\n      data-slot=\"header\"\n      className={cn(\"flex flex-col gap-1\", className)}\n      {...props}\n    >\n      {title ? (\n        <div data-control-ui=\"environment-variables\" data-control-family=\"environment-variables\" data-slot=\"title\">\n          {title}\n        </div>\n      ) : null}\n      {description ? (\n        <div\n          data-control-ui=\"environment-variables\"\n          data-control-family=\"environment-variables\"\n          data-slot=\"description\"\n          className=\"max-w-2xl\"\n        >\n          {description}\n        </div>\n      ) : null}\n    </div>\n  );\n}\n\nexport type EnvironmentVariablesToolbarProps = ComponentProps<\"div\"> & { style?: CSSProperties & EnvironmentVariablesKnobStyle };\n\nexport function EnvironmentVariablesToolbar({ className, children, ...props }: EnvironmentVariablesToolbarProps) {\n  const { readOnly } = useEnvironmentVariablesContext(\"EnvironmentVariables.Toolbar\");\n  if (readOnly) return null;\n\n  return (\n    <div\n      data-control-ui=\"environment-variables\"\n      data-control-family=\"environment-variables\"\n      data-slot=\"toolbar\"\n      className={cn(\"flex min-w-0 flex-col gap-2 sm:flex-row sm:items-center\", className)}\n      {...props}\n    >\n      {children ?? (\n        <>\n          <p\n            data-control-ui=\"environment-variables\"\n            data-control-family=\"environment-variables\"\n            data-slot=\"hint\"\n            className=\"min-w-0 flex-1\"\n          >\n            Paste one or more{\" \"}\n            <code data-control-ui=\"environment-variables\" data-control-family=\"environment-variables\" data-slot=\"hint-code\">\n              KEY=value\n            </code>{\" \"}\n            lines directly into any field.\n          </p>\n          <EnvironmentVariablesUploadButton />\n        </>\n      )}\n    </div>\n  );\n}\n\nexport type EnvironmentVariablesUploadButtonProps = ComponentProps<typeof Button> & {\n  inputLabel?: string;\n};\n\nexport function EnvironmentVariablesUploadButton({\n  inputLabel = \"Import .env file\",\n  children = \"Upload .env\",\n  variant = \"surface\",\n  size = \"sm\",\n  disabled,\n  onClick,\n  ...props\n}: EnvironmentVariablesUploadButtonProps) {\n  const { editor, disabled: contextDisabled, readOnly } = useEnvironmentVariablesContext(\"EnvironmentVariables.UploadButton\");\n  const inputId = useId();\n  const isDisabled = disabled ?? (contextDisabled || readOnly);\n\n  function handleChange(event: ChangeEvent<HTMLInputElement>) {\n    void editor.handleFileUpload(event);\n  }\n\n  return (\n    <>\n      <input\n        id={inputId}\n        type=\"file\"\n        accept=\".env,text/plain\"\n        className=\"sr-only\"\n        disabled={isDisabled}\n        aria-label={inputLabel}\n        onChange={handleChange}\n      />\n      <Button\n        type=\"button\"\n        variant={variant}\n        size={size}\n        disabled={isDisabled}\n        onClick={(event) => {\n          onClick?.(event);\n          if (!event.defaultPrevented) event.currentTarget.ownerDocument.getElementById(inputId)?.click();\n        }}\n        {...props}\n      >\n        <FileUpIcon aria-hidden=\"true\" className=\"size-3.5\" />\n        {children}\n      </Button>\n    </>\n  );\n}\n\nexport type EnvironmentVariablesRowsProps = ComponentProps<\"div\"> & {\n  rowErrors?: EnvironmentVariablesRowErrors;\n} & { style?: CSSProperties & EnvironmentVariablesKnobStyle };\n\nexport function EnvironmentVariablesRows({ rowErrors, className, children, ...props }: EnvironmentVariablesRowsProps) {\n  const { editor, keyLabel, valueLabel, readOnly } = useEnvironmentVariablesContext(\"EnvironmentVariables.Rows\");\n\n  return (\n    <div\n      data-control-ui=\"environment-variables\"\n      data-control-family=\"environment-variables\"\n      data-slot=\"rows\"\n      className={cn(\"min-w-0\", className)}\n      {...props}\n    >\n      {children ?? (\n        <>\n          <div\n            aria-hidden=\"true\"\n            data-control-ui=\"environment-variables\"\n            data-control-family=\"environment-variables\"\n            data-slot=\"column-labels\"\n            className={cn(\n              \"hidden min-w-0 grid-cols-[minmax(0,1fr)_minmax(0,1.2fr)_auto] gap-2 px-1 pb-1 sm:grid\",\n              readOnly && \"grid-cols-[minmax(0,1fr)_minmax(0,1.2fr)]\",\n            )}\n          >\n            <span>{keyLabel}</span>\n            <span>{valueLabel}</span>\n            {!readOnly ? <span className=\"size-[var(--control-h-sm)]\" /> : null}\n          </div>\n          {editor.rows.map((row, index) => (\n            <EnvironmentVariablesRow key={editor.getRowId(index)} row={row} index={index} rowErrors={rowErrors} />\n          ))}\n        </>\n      )}\n    </div>\n  );\n}\n\nexport type EnvironmentVariablesRowProps = Omit<ComponentProps<\"div\">, \"children\"> & {\n  row: EnvironmentVariableRow;\n  index: number;\n  rowErrors?: EnvironmentVariablesRowErrors;\n} & { style?: CSSProperties & EnvironmentVariablesKnobStyle };\n\nexport function EnvironmentVariablesRow({ row, index, rowErrors, className, ...props }: EnvironmentVariablesRowProps) {\n  const {\n    editor,\n    disabled,\n    readOnly,\n    rowErrors: contextRowErrors,\n    keyLabel,\n    valueLabel,\n    keyPlaceholder,\n    valuePlaceholder,\n    duplicateKeyMessage,\n  } = useEnvironmentVariablesContext(\"EnvironmentVariables.Row\");\n  const resolvedRowErrors = rowErrors ?? contextRowErrors;\n  const keyError = resolvedRowErrors?.[index]?.key ?? (editor.rowHasDuplicateKey(index) ? duplicateKeyMessage : null);\n  const valueError = resolvedRowErrors?.[index]?.value;\n  const isDisabled = disabled || readOnly;\n  const rowId = editor.getRowId(index);\n  const keyInputId = `${rowId}-key`;\n  const valueInputId = `${rowId}-value`;\n  const revealValueLabel = editor.isValueRevealed(index) ? \"Hide value\" : \"Show value\";\n  const removeRowLabel = `Remove environment variable ${row.key.trim() || index + 1}`;\n\n  function handlePaste(event: ClipboardEvent<HTMLInputElement>) {\n    if (editor.handlePaste(index, event.clipboardData.getData(\"text\"))) {\n      event.preventDefault();\n    }\n  }\n\n  return (\n    <div\n      data-control-ui=\"environment-variables\"\n      data-control-family=\"environment-variables\"\n      data-slot=\"row\"\n      className={cn(\n        \"relative grid min-w-0 gap-3 py-2 pr-9 pl-1 sm:grid-cols-[minmax(0,1fr)_minmax(0,1.2fr)_auto] sm:gap-2 sm:px-1\",\n        className,\n      )}\n      {...props}\n    >\n      <div className=\"flex min-w-0 flex-col gap-1.5\">\n        <label\n          data-control-ui=\"environment-variables\"\n          data-control-family=\"environment-variables\"\n          data-slot=\"field-label\"\n          htmlFor={keyInputId}\n          className=\"sm:sr-only\"\n        >\n          {keyLabel}\n        </label>\n        <Input\n          data-control-ui=\"environment-variables\"\n          data-control-family=\"environment-variables\"\n          data-slot=\"key-input\"\n          id={keyInputId}\n          size=\"sm\"\n          value={row.key}\n          placeholder={keyPlaceholder}\n          disabled={isDisabled}\n          aria-invalid={Boolean(keyError)}\n          onChange={(event) => editor.updateRow(index, { key: event.target.value })}\n          onPaste={handlePaste}\n        />\n        {keyError ? (\n          <p data-control-ui=\"environment-variables\" data-control-family=\"environment-variables\" data-slot=\"field-error\">\n            {keyError}\n          </p>\n        ) : null}\n      </div>\n\n      <div className=\"flex min-w-0 flex-col gap-1.5\">\n        <label\n          data-control-ui=\"environment-variables\"\n          data-control-family=\"environment-variables\"\n          data-slot=\"field-label\"\n          htmlFor={valueInputId}\n          className=\"sm:sr-only\"\n        >\n          {valueLabel}\n        </label>\n        <InputGroup\n          data-control-ui=\"environment-variables\"\n          data-control-family=\"environment-variables\"\n          data-slot=\"value-group\"\n          data-invalid={valueError ? \"true\" : undefined}\n          size=\"sm\"\n        >\n          <InputGroupInput\n            data-control-ui=\"environment-variables\"\n            data-control-family=\"environment-variables\"\n            data-slot=\"value-input\"\n            id={valueInputId}\n            value={row.value}\n            placeholder={valuePlaceholder}\n            disabled={isDisabled}\n            aria-invalid={Boolean(valueError)}\n            type={editor.isValueRevealed(index) ? \"text\" : \"password\"}\n            onChange={(event) => editor.updateRow(index, { value: event.target.value })}\n            onPaste={handlePaste}\n          />\n          <InputGroupAddon className=\"pr-1\">\n            <Button\n              type=\"button\"\n              variant=\"quiet\"\n              size=\"sm\"\n              iconOnly\n              disabled={isDisabled}\n              aria-label={revealValueLabel}\n              title={revealValueLabel}\n              onClick={() => editor.toggleValueVisibility(index)}\n            >\n              {editor.isValueRevealed(index) ? (\n                <EyeOffIcon aria-hidden=\"true\" className=\"size-3.5\" />\n              ) : (\n                <EyeIcon aria-hidden=\"true\" className=\"size-3.5\" />\n              )}\n            </Button>\n          </InputGroupAddon>\n        </InputGroup>\n        {valueError ? (\n          <p data-control-ui=\"environment-variables\" data-control-family=\"environment-variables\" data-slot=\"field-error\">\n            {valueError}\n          </p>\n        ) : null}\n      </div>\n\n      {!readOnly ? (\n        <Button\n          type=\"button\"\n          variant=\"quiet\"\n          tone=\"danger\"\n          size=\"sm\"\n          iconOnly\n          disabled={disabled}\n          aria-label={removeRowLabel}\n          title=\"Remove variable\"\n          className=\"absolute top-2 right-1 sm:static sm:justify-self-end sm:self-start\"\n          onClick={() => editor.removeRow(index)}\n        >\n          <Trash2Icon aria-hidden=\"true\" className=\"size-3.5\" />\n        </Button>\n      ) : null}\n    </div>\n  );\n}\n\nexport type EnvironmentVariablesAddButtonProps = ComponentProps<typeof Button>;\n\nexport function EnvironmentVariablesAddButton({\n  children = \"Add variable\",\n  variant = \"surface\",\n  size = \"sm\",\n  disabled,\n  onClick,\n  ...props\n}: EnvironmentVariablesAddButtonProps) {\n  const { editor, disabled: contextDisabled, readOnly } = useEnvironmentVariablesContext(\"EnvironmentVariables.AddButton\");\n  if (readOnly) return null;\n\n  return (\n    <Button\n      type=\"button\"\n      variant={variant}\n      size={size}\n      disabled={disabled ?? contextDisabled}\n      onClick={(event) => {\n        onClick?.(event);\n        if (!event.defaultPrevented) editor.appendRow();\n      }}\n      {...props}\n    >\n      <PlusIcon aria-hidden=\"true\" className=\"size-3.5\" />\n      {children}\n    </Button>\n  );\n}\n\nexport type EnvironmentVariablesResetButtonProps = ComponentProps<typeof Button>;\n\nexport function EnvironmentVariablesResetButton({\n  children = \"Reset\",\n  variant = \"quiet\",\n  size = \"sm\",\n  disabled,\n  onClick,\n  ...props\n}: EnvironmentVariablesResetButtonProps) {\n  const { editor, disabled: contextDisabled, readOnly } = useEnvironmentVariablesContext(\"EnvironmentVariables.ResetButton\");\n  if (readOnly) return null;\n\n  return (\n    <Button\n      type=\"button\"\n      variant={variant}\n      size={size}\n      disabled={disabled ?? (contextDisabled || !editor.isDirty)}\n      onClick={(event) => {\n        onClick?.(event);\n        if (!event.defaultPrevented) editor.resetRows();\n      }}\n      {...props}\n    >\n      <RotateCcwIcon aria-hidden=\"true\" className=\"size-3.5\" />\n      {children}\n    </Button>\n  );\n}\n\nexport type EnvironmentVariablesSubmitButtonProps = ComponentProps<typeof Button>;\n\nexport function EnvironmentVariablesSubmitButton({\n  children = \"Save\",\n  variant = \"solid\",\n  tone = \"primary\",\n  size = \"sm\",\n  disabled,\n  ...props\n}: EnvironmentVariablesSubmitButtonProps) {\n  const { editor, disabled: contextDisabled, readOnly } = useEnvironmentVariablesContext(\"EnvironmentVariables.SubmitButton\");\n  if (readOnly) return null;\n\n  return (\n    <Button\n      type=\"submit\"\n      variant={variant}\n      tone={tone}\n      size={size}\n      disabled={disabled ?? (contextDisabled || editor.hasDuplicateKeys)}\n      {...props}\n    >\n      <SaveIcon aria-hidden=\"true\" className=\"size-3.5\" />\n      {children}\n    </Button>\n  );\n}\n\nexport type EnvironmentVariablesActionsProps = ComponentProps<\"div\"> & { style?: CSSProperties & EnvironmentVariablesKnobStyle };\n\nexport function EnvironmentVariablesActions({ className, ...props }: EnvironmentVariablesActionsProps) {\n  return (\n    <div\n      data-control-ui=\"environment-variables\"\n      data-control-family=\"environment-variables\"\n      data-slot=\"actions\"\n      className={cn(\"flex min-w-0 items-center justify-between gap-2\", className)}\n      {...props}\n    />\n  );\n}\n\nexport type EnvironmentVariablesMessageProps = Omit<ComponentProps<\"div\">, \"style\"> & {\n  error?: ReactNode;\n  style?: CSSProperties & EnvironmentVariablesKnobStyle;\n};\n\nexport function EnvironmentVariablesMessage({ error, className, children, ...props }: EnvironmentVariablesMessageProps) {\n  const content = children ?? error;\n  if (!content) return null;\n\n  return (\n    <div\n      role=\"alert\"\n      data-control-ui=\"environment-variables\"\n      data-control-family=\"environment-variables\"\n      data-slot=\"message\"\n      className={cn(\"flex items-start gap-2 px-3 py-2\", className)}\n      {...props}\n    >\n      <AlertTriangleIcon aria-hidden=\"true\" className=\"mt-0.5 size-3.5 shrink-0\" />\n      <span className=\"min-w-0\">{content}</span>\n    </div>\n  );\n}\n\nexport type EnvironmentVariablesDuplicateKeysErrorProps = Omit<EnvironmentVariablesMessageProps, \"error\"> & {\n  message?: ReactNode;\n};\n\nexport function EnvironmentVariablesDuplicateKeysError({ message, ...props }: EnvironmentVariablesDuplicateKeysErrorProps) {\n  const { editor, duplicateKeyMessage } = useEnvironmentVariablesContext(\"EnvironmentVariables.DuplicateKeysError\");\n  if (!editor.hasDuplicateKeys) return null;\n\n  return <EnvironmentVariablesMessage error={message ?? duplicateKeyMessage} {...props} />;\n}\n\nexport type EnvironmentVariablesUploadErrorProps = Omit<EnvironmentVariablesMessageProps, \"error\">;\n\nexport function EnvironmentVariablesUploadError(props: EnvironmentVariablesUploadErrorProps) {\n  const { editor } = useEnvironmentVariablesContext(\"EnvironmentVariables.UploadError\");\n  return <EnvironmentVariablesMessage error={editor.uploadError} {...props} />;\n}\n\nexport type EnvironmentVariablesReadOnlyListProps = ComponentProps<\"div\"> & {\n  rows?: readonly EnvironmentVariableRow[];\n  emptyMessage?: ReactNode;\n} & { style?: CSSProperties & EnvironmentVariablesKnobStyle };\n\nexport function EnvironmentVariablesReadOnlyList({\n  rows,\n  emptyMessage = \"No environment variables\",\n  className,\n  children,\n  ...props\n}: EnvironmentVariablesReadOnlyListProps) {\n  const context = use(EnvironmentVariablesContext);\n  const resolvedRows = rows ?? context?.editor.rows ?? [];\n  const filledRows = resolvedRows.filter((row) => row.key.trim());\n\n  return (\n    <div\n      data-control-ui=\"environment-variables\"\n      data-control-family=\"environment-variables\"\n      data-slot=\"readonly-list\"\n      className={cn(\"flex flex-col gap-1\", className)}\n      {...props}\n    >\n      {children ??\n        (filledRows.length > 0 ? (\n          filledRows.map((row) => <EnvironmentVariablesReadOnlyItem key={row.key} name={row.key} value={row.value} />)\n        ) : (\n          <div data-control-ui=\"environment-variables\" data-control-family=\"environment-variables\" data-slot=\"empty\" className=\"px-3 py-4\">\n            {emptyMessage}\n          </div>\n        ))}\n    </div>\n  );\n}\n\nexport type EnvironmentVariablesReadOnlyItemProps = Omit<ComponentProps<\"div\">, \"style\"> & {\n  name: ReactNode;\n  value?: ReactNode;\n  revealed?: boolean;\n  style?: CSSProperties & EnvironmentVariablesKnobStyle;\n};\n\nexport function EnvironmentVariablesReadOnlyItem({\n  name,\n  value = \"********\",\n  revealed = false,\n  className,\n  ...props\n}: EnvironmentVariablesReadOnlyItemProps) {\n  return (\n    <div\n      data-control-ui=\"environment-variables\"\n      data-control-family=\"environment-variables\"\n      data-slot=\"readonly-item\"\n      className={cn(\"grid min-w-0 grid-cols-[minmax(0,0.9fr)_minmax(0,1.1fr)] gap-3 px-1 py-2\", className)}\n      {...props}\n    >\n      <span\n        data-control-ui=\"environment-variables\"\n        data-control-family=\"environment-variables\"\n        data-slot=\"readonly-key\"\n        className=\"min-w-0 truncate\"\n      >\n        {name}\n      </span>\n      <span\n        data-control-ui=\"environment-variables\"\n        data-control-family=\"environment-variables\"\n        data-slot=\"readonly-value\"\n        className=\"min-w-0 truncate\"\n      >\n        {revealed ? value : \"********\"}\n      </span>\n    </div>\n  );\n}\n\nexport const EnvironmentVariables = Object.assign(EnvironmentVariablesComponent, {\n  Root: EnvironmentVariablesRoot,\n  Header: EnvironmentVariablesHeader,\n  Toolbar: EnvironmentVariablesToolbar,\n  UploadButton: EnvironmentVariablesUploadButton,\n  UploadError: EnvironmentVariablesUploadError,\n  Rows: EnvironmentVariablesRows,\n  Row: EnvironmentVariablesRow,\n  AddButton: EnvironmentVariablesAddButton,\n  ResetButton: EnvironmentVariablesResetButton,\n  SubmitButton: EnvironmentVariablesSubmitButton,\n  Actions: EnvironmentVariablesActions,\n  Message: EnvironmentVariablesMessage,\n  DuplicateKeysError: EnvironmentVariablesDuplicateKeysError,\n  ReadOnlyList: EnvironmentVariablesReadOnlyList,\n  ReadOnlyItem: EnvironmentVariablesReadOnlyItem,\n});\n"
    },
    {
      "path": "src/registry/sources/control-ui/recipes/environment-variables.css",
      "target": "@components/control-ui/styles/recipes/environment-variables.css",
      "type": "registry:file",
      "content": "@layer components {\n  :where([data-control-family=\"environment-variables\"][data-slot=\"root\"]) {\n    --cui-environment-variables-error-foreground: var(--destructive-text);\n    --cui-environment-variables-message-background: oklch(from var(--destructive) l c h / 0.05);\n    --cui-environment-variables-message-border-color: oklch(from var(--destructive) l c h / 0.4);\n    --cui-environment-variables-message-foreground: var(--destructive-text);\n    --cui-environment-variables-meta-foreground: var(--muted-foreground);\n    --cui-environment-variables-title-foreground: var(--foreground);\n  }\n  :where([data-control-family=\"environment-variables\"][data-slot=\"title\"]) {\n    color: var(--cui-environment-variables-title-foreground);\n    font-size: var(--text-label);\n    font-weight: var(--font-weight-semibold);\n  }\n\n  :where(\n    [data-control-family=\"environment-variables\"]:is(\n      [data-slot=\"description\"],\n      [data-slot=\"hint\"],\n      [data-slot=\"column-labels\"],\n      [data-slot=\"field-label\"],\n      [data-slot=\"empty\"],\n      [data-slot=\"readonly-item\"]\n    )\n  ) {\n    color: var(--cui-environment-variables-meta-foreground);\n    font-size: var(--text-caption);\n  }\n\n  :where([data-control-family=\"environment-variables\"][data-slot=\"hint-code\"]),\n  :where([data-control-family=\"environment-variables\"][data-slot=\"readonly-key\"]) {\n    color: var(--foreground);\n  }\n\n  :where([data-control-family=\"environment-variables\"]:is([data-slot=\"key-input\"], [data-slot=\"value-input\"])) {\n    font-size: var(--text-caption);\n  }\n\n  :where([data-control-family=\"environment-variables\"][data-slot=\"value-group\"][data-invalid=\"true\"]) {\n    box-shadow: 0 0 0 2px oklch(from var(--destructive) l c h / 0.7);\n  }\n\n  :where([data-control-family=\"environment-variables\"][data-slot=\"field-error\"]) {\n    color: var(--cui-environment-variables-error-foreground);\n    font-size: var(--text-caption);\n  }\n\n  :where([data-control-family=\"environment-variables\"][data-slot=\"message\"]) {\n    background: var(--cui-environment-variables-message-background);\n    border-block: 1px solid var(--cui-environment-variables-message-border-color);\n    color: var(--cui-environment-variables-message-foreground);\n    font-size: var(--text-caption);\n  }\n\n  :where([data-control-family=\"environment-variables\"][data-slot=\"readonly-value\"]) {\n    color: var(--muted-foreground);\n    font-family: var(--font-mono);\n  }\n\n  :where([data-control-family=\"environment-variables\"][data-slot=\"readonly-key\"]) {\n    font-family: var(--font-mono);\n    font-weight: var(--font-weight-medium);\n  }\n\n  :where([data-control-family=\"environment-variables\"][data-slot=\"empty\"]) {\n    text-align: center;\n  }\n\n  :where([data-control-family=\"environment-variables\"][data-slot=\"value-input\"]) {\n    font-family: var(--font-mono);\n  }\n\n  :where([data-control-family=\"environment-variables\"][data-slot=\"field-label\"]) {\n    font-weight: var(--font-weight-medium);\n  }\n\n  :where([data-control-family=\"environment-variables\"][data-slot=\"key-input\"]) {\n    font-family: var(--font-mono);\n  }\n\n  :where([data-control-family=\"environment-variables\"][data-slot=\"column-labels\"]) {\n    font-weight: var(--font-weight-medium);\n  }\n\n  :where([data-control-family=\"environment-variables\"][data-slot=\"hint-code\"]) {\n    font-family: var(--font-mono);\n  }\n}\n\n@property --cui-environment-variables-title-foreground {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-environment-variables-meta-foreground {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-environment-variables-error-foreground {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-environment-variables-message-background {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-environment-variables-message-foreground {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n\n@property --cui-environment-variables-message-border-color {\n  syntax: \"<color>\";\n  inherits: true;\n  initial-value: transparent;\n}\n"
    }
  ],
  "css": {
    "@import \"../components/control-ui/styles/recipes/environment-variables.css\"": {}
  },
  "meta": {}
}
