@x12i/fieldkit 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/theme.tsx","../src/infer.ts","../src/resolve.ts","../src/icons.tsx","../src/components/FieldRenderer.tsx","../src/components/RecordView.tsx"],"sourcesContent":["export { FieldKitProvider, useFieldKitTheme, defaultTheme, TONE_PALETTE, hashTone, toneHex } from './theme';\nexport type { FieldKitTheme, FieldKitThemeOverride, ToneName } from './theme';\n\nexport { labelFromKey, defaultClassifySignal, inferRule } from './infer';\nexport { resolveField } from './resolve';\nexport type { ResolveOptions } from './resolve';\n\nexport { FieldRenderer } from './components/FieldRenderer';\nexport type { FieldRendererProps } from './components/FieldRenderer';\n\nexport { RecordView } from './components/RecordView';\nexport type { RecordViewProps } from './components/RecordView';\n\nexport * from './icons';\n\nexport type {\n FieldKind,\n FieldRule,\n FieldRules,\n ResolvedField,\n ResolvedItem,\n BannerContent,\n ClassifySignal,\n ToneMap,\n} from './types';\n","import * as React from 'react';\n\n/** Named tones the palette guarantees. A theme can rename their hexes; consumer\n * toneMaps point AT these names (e.g. { REQUIRE_INFORMATION: 'amber' }), never at hex. */\nexport const TONE_PALETTE = {\n neutral: '#8B96A3',\n amber: '#E8A33D',\n rose: '#E2564F',\n green: '#59B87B',\n blue: '#6FA8DC',\n violet: '#9B8CDB',\n teal: '#4FB6A8',\n} as const;\n\nexport type ToneName = keyof typeof TONE_PALETTE;\n\nconst HASHABLE_TONES: ToneName[] = ['amber', 'rose', 'green', 'blue', 'violet', 'teal'];\n\n/** Deterministic fallback so an enum value nobody mapped yet still gets a stable,\n * distinct color instead of falling back to the same neutral gray every time. */\nexport function hashTone(value: string): ToneName {\n let h = 0;\n for (let i = 0; i < value.length; i++) h = (h * 31 + value.charCodeAt(i)) >>> 0;\n return HASHABLE_TONES[h % HASHABLE_TONES.length];\n}\n\n/** `colors.accent` and every value in `tonePalette` must be 6-digit hex ('#rrggbb'):\n * FieldRenderer and RecordView tint them for badges/boxes by appending 2 hex digits\n * of alpha ('${hex}24'), which only produces valid CSS for that format. The rest of\n * `colors` is used as-is and accepts any valid CSS color. */\nexport interface FieldKitTheme {\n colors: {\n background: string;\n surface: string;\n surfaceMuted: string;\n border: string;\n textPrimary: string;\n textSecondary: string;\n textMuted: string;\n accent: string;\n };\n fonts: {\n heading: string;\n body: string;\n mono: string;\n };\n radii: {\n sm: string;\n md: string;\n lg: string;\n pill: string;\n };\n tonePalette: Record<string, string>;\n}\n\nexport const defaultTheme: FieldKitTheme = {\n colors: {\n background: '#0A0E13',\n surface: '#10161D',\n surfaceMuted: '#0D1218',\n border: 'rgba(255,255,255,0.07)',\n textPrimary: '#F4F6F8',\n textSecondary: '#C7CDD3',\n textMuted: '#5B6672',\n accent: '#4FB6A8',\n },\n fonts: {\n heading: \"'Space Grotesk', ui-sans-serif, sans-serif\",\n body: \"'IBM Plex Sans', ui-sans-serif, sans-serif\",\n mono: \"'IBM Plex Mono', ui-monospace, monospace\",\n },\n radii: { sm: '5px', md: '8px', lg: '14px', pill: '999px' },\n tonePalette: TONE_PALETTE,\n};\n\nexport type FieldKitThemeOverride = {\n [K in keyof FieldKitTheme]?: Partial<FieldKitTheme[K]>;\n};\n\nfunction mergeTheme(base: FieldKitTheme, override?: FieldKitThemeOverride): FieldKitTheme {\n if (!override) return base;\n return {\n colors: { ...base.colors, ...override.colors },\n fonts: { ...base.fonts, ...override.fonts },\n radii: { ...base.radii, ...override.radii },\n tonePalette: { ...base.tonePalette, ...override.tonePalette } as Record<string, string>,\n };\n}\n\nconst ThemeContext = React.createContext<FieldKitTheme>(defaultTheme);\n\nexport interface FieldKitProviderProps {\n theme?: FieldKitThemeOverride;\n children: React.ReactNode;\n}\n\n/** Wrap your app (or just the part that uses fieldkit) in this to change colors,\n * fonts, or radii. Anything you don't override keeps the default. */\nexport function FieldKitProvider({ theme, children }: FieldKitProviderProps) {\n const parent = React.useContext(ThemeContext);\n const merged = React.useMemo(() => mergeTheme(parent, theme), [parent, theme]);\n return <ThemeContext.Provider value={merged}>{children}</ThemeContext.Provider>;\n}\n\nexport function useFieldKitTheme(): FieldKitTheme {\n return React.useContext(ThemeContext);\n}\n\nexport function toneHex(theme: FieldKitTheme, toneKey: string | undefined): string {\n if (!toneKey) return theme.tonePalette.neutral ?? TONE_PALETTE.neutral;\n return theme.tonePalette[toneKey] ?? TONE_PALETTE.neutral;\n}\n","import type { ClassifySignal, FieldKind, FieldRule } from './types';\n\n/** camelCase / snake_case -> \"Title Case\", used both for auto-labeling leftover\n * fields and for humanizing enum values (\"REQUIRE_INFORMATION\" -> \"Require Information\"). */\nexport function labelFromKey(key: string): string {\n const spaced = key\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/_/g, ' ')\n .toLowerCase();\n return spaced\n .split(' ')\n .filter(Boolean)\n .map((w) => w.charAt(0).toUpperCase() + w.slice(1))\n .join(' ');\n}\n\n/** Default severity heuristic for a 'signals' field: sentences that assert something\n * is unresolved/unknown/missing read as a warning; everything else as a plain note.\n * Override via RecordView's `classifySignal` prop if your domain needs sharper rules. */\nexport const defaultClassifySignal: ClassifySignal = (text) => {\n const t = text.toLowerCase();\n const flagged = t.includes('unresolved') || t.includes('unknown') || t.includes('missing');\n return flagged ? 'warn' : 'info';\n};\n\nconst ENUM_LIKE = /^[A-Z0-9]+(_[A-Z0-9]+)*$/;\n\n/** The leftover handler: infers a render kind from the VALUE's shape alone, for any\n * key your FieldRules map doesn't (yet) cover. Same kinds as the curated map — this\n * is a safety net, not a second taxonomy. */\nexport function inferRule(key: string, value: unknown): FieldRule {\n const label = labelFromKey(key);\n const as = ((): FieldKind => {\n if (value === null || value === undefined || value === '') return 'empty';\n if (typeof value === 'boolean') return 'flag';\n if (typeof value === 'number') return 'stat';\n if (typeof value === 'string') {\n if (ENUM_LIKE.test(value)) return 'pill';\n if (value.length > 70 || /[.!?]\\s/.test(value)) return 'lead';\n if (!value.includes(' ') && value.length > 12) return 'mono';\n return 'text';\n }\n if (Array.isArray(value)) {\n if (value.length === 0) return 'empty';\n if (value.every((v) => typeof v === 'string')) {\n if ((value as string[]).every((v) => /\\?\\s*$/.test(v))) return 'qa';\n if ((value as string[]).some((v) => v.split(' ').length > 6)) return 'bullets';\n return 'tags';\n }\n return 'json';\n }\n return 'json';\n })();\n return { label, as };\n}\n","import { hashTone } from './theme';\nimport { labelFromKey, defaultClassifySignal } from './infer';\nimport type { ClassifySignal, FieldRule, ResolvedField, ToneMap } from './types';\n\nexport interface ResolveOptions {\n isLeftover?: boolean;\n classifySignal?: ClassifySignal;\n toneMap?: ToneMap;\n}\n\nfunction resolveTone(value: string, toneMap?: ToneMap): string {\n return toneMap?.[value] ?? hashTone(value);\n}\n\n/** Turns one (key, value, rule) triple into everything FieldRenderer needs — the one\n * place that understands what each FieldKind's payload looks like. Used identically\n * for curated fields (rule from your FieldRules map) and leftovers (rule from inferRule). */\nexport function resolveField(\n key: string,\n value: unknown,\n rule: FieldRule,\n options: ResolveOptions = {}\n): ResolvedField {\n const label = rule.label ?? labelFromKey(key);\n const classifySignal = options.classifySignal ?? defaultClassifySignal;\n const base: ResolvedField = {\n key,\n label,\n kind: rule.as,\n isLeftover: options.isLeftover ?? false,\n };\n\n switch (rule.as) {\n case 'lead':\n return { ...base, text: String(value ?? '') };\n case 'bullets':\n return { ...base, items: ((value as string[]) ?? []).map((t) => ({ text: t })) };\n case 'signals':\n return {\n ...base,\n items: ((value as string[]) ?? []).map((t) => {\n const s = classifySignal(t);\n return { text: t, warn: s === 'warn', info: s === 'info' };\n }),\n };\n case 'qa':\n return { ...base, items: ((value as string[]) ?? []).map((t) => ({ text: t })) };\n case 'pill': {\n const raw = String(value ?? '');\n return { ...base, pillText: labelFromKey(raw.toLowerCase()), toneKey: resolveTone(raw, options.toneMap) };\n }\n case 'mono':\n return { ...base, value: String(value ?? '') };\n case 'text':\n return { ...base, value: String(value ?? '') };\n case 'flag':\n return { ...base, flagOn: !!value, flagText: value ? 'Yes' : 'No' };\n case 'stat':\n return { ...base, value: String(value ?? '') };\n case 'tags':\n return { ...base, items: ((value as string[]) ?? []).map((t) => ({ text: t })) };\n case 'empty':\n return base;\n case 'json':\n default:\n return { ...base, kind: 'json', value: JSON.stringify(value, null, 2) };\n }\n}\n","import * as React from 'react';\n\nexport interface IconProps {\n size?: number;\n color?: string;\n style?: React.CSSProperties;\n}\n\nconst base = (size: number, color: string, style?: React.CSSProperties): React.SVGProps<SVGSVGElement> => ({\n width: size,\n height: size,\n viewBox: '0 0 24 24',\n fill: 'none',\n stroke: color,\n strokeWidth: 1.8,\n strokeLinecap: 'round',\n strokeLinejoin: 'round',\n style,\n});\n\nexport function CheckIcon({ size = 15, color = 'currentColor', style }: IconProps) {\n return (\n <svg {...base(size, color, style)}>\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <path d=\"M8 12.3l2.6 2.6L16 9.3\" />\n </svg>\n );\n}\n\nexport function WarningIcon({ size = 15, color = '#E8A33D', style }: IconProps) {\n return (\n <svg {...base(size, color, style)}>\n <path d=\"M12 3 2 21h20L12 3z\" />\n <path d=\"M12 9.5v5\" />\n <circle cx=\"12\" cy=\"17.2\" r=\"0.9\" fill={color} stroke=\"none\" />\n </svg>\n );\n}\n\nexport function InfoIcon({ size = 15, color = '#7C8894', style }: IconProps) {\n return (\n <svg {...base(size, color, style)}>\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <line x1=\"12\" y1=\"10.5\" x2=\"12\" y2=\"16.2\" />\n <circle cx=\"12\" cy=\"7.6\" r=\"0.9\" fill={color} stroke=\"none\" />\n </svg>\n );\n}\n\nexport function QuestionIcon({ size = 15, color = 'currentColor', style }: IconProps) {\n return (\n <svg {...base(size, color, style)}>\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <path d=\"M9.5 9.3a2.5 2.5 0 1 1 3.5 2.3c-.9.5-1 1-1 1.9\" />\n <circle cx=\"12\" cy=\"17\" r=\"0.9\" fill={color} stroke=\"none\" />\n </svg>\n );\n}\n\nexport function ChevronDownIcon({ size = 14, color = 'currentColor', style }: IconProps) {\n return (\n <svg {...base(size, color, style)}>\n <path d=\"M6 9l6 6 6-6\" />\n </svg>\n );\n}\n","import * as React from 'react';\nimport { toneHex, useFieldKitTheme } from '../theme';\nimport { CheckIcon, InfoIcon, QuestionIcon, WarningIcon } from '../icons';\nimport type { ResolvedField } from '../types';\n\nfunction assertNever(x: never): never {\n throw new Error('Unhandled FieldKind: ' + JSON.stringify(x));\n}\n\nexport interface FieldRendererProps {\n field: ResolvedField;\n /** 'content' = the full-weight treatment (bigger text, icons, tinted boxes) for\n * primary content. 'compact' = the label-over-value treatment used for diagnostics\n * and leftovers. Both cover all twelve kinds — nothing renders blank because of\n * which tier a field ended up in. */\n variant?: 'content' | 'compact';\n /** Renders the field's label above its value. RecordView turns this off when it\n * wants to draw the label itself (e.g. inside a shared row layout). */\n showLabel?: boolean;\n}\n\n/** Renders exactly one resolved field. This is the only place that knows what each\n * FieldKind looks like — every tier (content / diagnostics / leftovers) calls the\n * same component, so a kind can never be supported in one place and forgotten in\n * another the way three separate hand-written blocks could silently drift apart. */\nexport function FieldRenderer({ field, variant = 'compact', showLabel = true }: FieldRendererProps) {\n const theme = useFieldKitTheme();\n const isContent = variant === 'content';\n\n const labelNode = showLabel ? (\n <span\n style={{\n fontFamily: theme.fonts.heading,\n fontSize: isContent ? 11.5 : 10.5,\n fontWeight: 600,\n letterSpacing: '0.08em',\n textTransform: 'uppercase',\n color: theme.colors.textMuted,\n }}\n >\n {field.label}\n </span>\n ) : null;\n\n let body: React.ReactNode;\n\n switch (field.kind) {\n case 'lead':\n body = (\n <p\n style={{\n margin: 0,\n fontSize: isContent ? 15 : 13,\n lineHeight: 1.6,\n color: isContent ? theme.colors.textSecondary : theme.colors.textMuted,\n }}\n >\n {field.text}\n </p>\n );\n break;\n\n case 'bullets':\n body = (\n <div style={{ display: 'flex', flexDirection: 'column', gap: isContent ? 10 : 6 }}>\n {field.items?.map((item, i) =>\n isContent ? (\n <div key={i} style={{ display: 'flex', gap: 10, alignItems: 'flex-start' }}>\n <CheckIcon size={15} color={theme.colors.accent} style={{ flexShrink: 0, marginTop: 3 }} />\n <span style={{ fontSize: 14, lineHeight: 1.55, color: theme.colors.textSecondary }}>{item.text}</span>\n </div>\n ) : (\n <div key={i} style={{ display: 'flex', gap: 8, alignItems: 'flex-start' }}>\n <span style={{ color: theme.colors.textMuted, fontSize: 13 }}>–</span>\n <span style={{ fontSize: 13, lineHeight: 1.5, color: theme.colors.textMuted }}>{item.text}</span>\n </div>\n )\n )}\n </div>\n );\n break;\n\n case 'signals':\n body = (\n <div style={{ display: 'flex', flexDirection: 'column', gap: isContent ? 8 : 6 }}>\n {field.items?.map((item, i) => (\n <div\n key={i}\n style={{\n display: 'flex',\n gap: 10,\n alignItems: 'flex-start',\n background: theme.colors.surfaceMuted,\n border: `1px solid ${theme.colors.border}`,\n borderRadius: isContent ? 8 : 6,\n padding: isContent ? '12px 14px' : '8px 10px',\n }}\n >\n {item.warn && <WarningIcon size={isContent ? 15 : 13} style={{ flexShrink: 0, marginTop: 2 }} />}\n {item.info && <InfoIcon size={isContent ? 15 : 13} style={{ flexShrink: 0, marginTop: 2 }} />}\n <span\n style={{\n fontSize: isContent ? 13.5 : 13,\n lineHeight: 1.55,\n color: isContent ? theme.colors.textSecondary : theme.colors.textMuted,\n }}\n >\n {item.text}\n </span>\n </div>\n ))}\n </div>\n );\n break;\n\n case 'qa':\n body = (\n <div\n style={\n isContent\n ? {\n display: 'flex',\n flexDirection: 'column',\n gap: 12,\n background: `${theme.colors.accent}12`,\n border: `1px solid ${theme.colors.accent}33`,\n borderRadius: 10,\n padding: '14px 16px',\n }\n : { display: 'flex', flexDirection: 'column', gap: 6 }\n }\n >\n {field.items?.map((item, i) => (\n <div key={i} style={{ display: 'flex', gap: isContent ? 10 : 8, alignItems: 'flex-start' }}>\n {isContent ? (\n <QuestionIcon size={15} color={theme.colors.accent} style={{ flexShrink: 0, marginTop: 2 }} />\n ) : (\n <span style={{ color: theme.colors.accent, fontSize: 13, fontWeight: 600 }}>?</span>\n )}\n <span\n style={{\n fontSize: isContent ? 14 : 13,\n lineHeight: 1.55,\n color: isContent ? theme.colors.textSecondary : theme.colors.textMuted,\n }}\n >\n {item.text}\n </span>\n </div>\n ))}\n </div>\n );\n break;\n\n case 'pill': {\n const hex = toneHex(theme, field.toneKey);\n body = (\n <span\n style={{\n alignSelf: 'flex-start',\n padding: '3px 9px',\n borderRadius: theme.radii.sm,\n fontSize: 11,\n fontWeight: 600,\n fontFamily: theme.fonts.heading,\n textTransform: 'uppercase',\n background: `${hex}24`,\n color: hex,\n border: `1px solid ${hex}55`,\n }}\n >\n {field.pillText}\n </span>\n );\n break;\n }\n\n case 'mono':\n body = (\n <span style={{ fontFamily: theme.fonts.mono, fontSize: isContent ? 13 : 12.5, color: theme.colors.textMuted }}>\n {field.value}\n </span>\n );\n break;\n\n case 'text':\n body = (\n <span\n style={{\n fontSize: isContent ? 14 : 13,\n lineHeight: 1.55,\n color: isContent ? theme.colors.textSecondary : theme.colors.textMuted,\n }}\n >\n {field.value}\n </span>\n );\n break;\n\n case 'flag': {\n const hex = field.flagOn ? theme.tonePalette.green ?? '#59B87B' : theme.colors.textMuted;\n body = (\n <span\n style={{\n alignSelf: 'flex-start',\n padding: '3px 9px',\n borderRadius: theme.radii.sm,\n fontSize: 11,\n fontWeight: 600,\n fontFamily: theme.fonts.heading,\n textTransform: 'uppercase',\n background: `${hex}24`,\n color: hex,\n border: `1px solid ${hex}55`,\n }}\n >\n {field.flagText}\n </span>\n );\n break;\n }\n\n case 'stat':\n body = (\n <span style={{ fontFamily: theme.fonts.mono, fontSize: isContent ? 16 : 15, color: theme.colors.textSecondary }}>\n {field.value}\n </span>\n );\n break;\n\n case 'tags':\n body = (\n <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>\n {field.items?.map((item, i) => (\n <span\n key={i}\n style={{\n background: theme.colors.surfaceMuted,\n color: theme.colors.textMuted,\n border: `1px solid ${theme.colors.border}`,\n padding: '3px 9px',\n borderRadius: theme.radii.sm,\n fontSize: 11.5,\n }}\n >\n {item.text}\n </span>\n ))}\n </div>\n );\n break;\n\n case 'empty':\n body = <span style={{ fontSize: 13, color: theme.colors.textMuted, opacity: 0.6 }}>— not provided —</span>;\n break;\n\n case 'json':\n body = (\n <pre\n style={{\n margin: 0,\n fontFamily: theme.fonts.mono,\n fontSize: 11.5,\n color: theme.colors.textMuted,\n whiteSpace: 'pre-wrap',\n background: theme.colors.background,\n border: `1px solid ${theme.colors.border}`,\n borderRadius: theme.radii.sm,\n padding: '10px 12px',\n }}\n >\n {field.value}\n </pre>\n );\n break;\n\n default:\n return assertNever(field.kind);\n }\n\n return (\n <div style={{ display: 'flex', flexDirection: 'column', gap: isContent ? 12 : 6 }}>\n {labelNode}\n {body}\n </div>\n );\n}\n","import * as React from 'react';\nimport { useFieldKitTheme } from '../theme';\nimport { inferRule } from '../infer';\nimport { resolveField } from '../resolve';\nimport { FieldRenderer } from './FieldRenderer';\nimport { ChevronDownIcon, WarningIcon } from '../icons';\nimport type { BannerContent, ClassifySignal, FieldRules, ToneMap } from '../types';\n\nexport interface RecordViewProps {\n /** The record to render. Any shape — only `fieldRules` + the key lists below decide meaning. */\n record: Record<string, unknown>;\n /** Your metadata map: field name -> how to render it. Extend this whenever you want a\n * field promoted out of the leftover bucket. */\n fieldRules: FieldRules;\n /** Keys rendered as primary content, in order, using the full-weight FieldRenderer variant. */\n contentKeys: string[];\n /** Keys rendered inside the collapsible diagnostics panel, using the compact variant. */\n diagnosticKeys: string[];\n /** Keys handled entirely by your own `header` render prop — excluded from generic\n * rendering (title, status, timestamps: whatever your header needs). */\n identityKeys?: string[];\n /** Renders the card's header (title, status pill, meta row, …). Domain-specific by\n * nature, so fieldkit doesn't guess at it. */\n header?: (record: Record<string, unknown>) => React.ReactNode;\n /** Derives an optional attention banner from the record — e.g. promote a broken\n * pipeline state to the top instead of leaving it buried in diagnostics. */\n banner?: (record: Record<string, unknown>) => BannerContent | null;\n /** Overrides the default warn/info heuristic used by 'signals' fields. */\n classifySignal?: ClassifySignal;\n /** Maps your domain's enum values (statuses, quality flags, …) to tone names. Anything\n * not listed here still gets a stable color via a deterministic hash, never a crash. */\n toneMap?: ToneMap;\n defaultDiagnosticsOpen?: boolean;\n diagnosticsLabel?: string;\n diagnosticsHint?: string;\n style?: React.CSSProperties;\n}\n\n/** Renders a record's content fields, plus a collapsible panel holding both the\n * curated diagnostic fields AND anything left over — every key in `record` ends up\n * somewhere, never silently dropped. */\nexport function RecordView({\n record,\n fieldRules,\n contentKeys,\n diagnosticKeys,\n identityKeys = [],\n header,\n banner,\n classifySignal,\n toneMap,\n defaultDiagnosticsOpen = false,\n diagnosticsLabel = 'Diagnostics',\n diagnosticsHint = 'Every other field this record reports, curated and unmapped alike',\n style,\n}: RecordViewProps) {\n const theme = useFieldKitTheme();\n const [open, setOpen] = React.useState(defaultDiagnosticsOpen);\n\n const contentFields = React.useMemo(\n () => contentKeys.map((key) => resolveField(key, record[key], fieldRules[key], { classifySignal, toneMap })),\n [record, fieldRules, contentKeys, classifySignal, toneMap]\n );\n\n const diagnosticFields = React.useMemo(\n () => diagnosticKeys.map((key) => resolveField(key, record[key], fieldRules[key], { classifySignal, toneMap })),\n [record, fieldRules, diagnosticKeys, classifySignal, toneMap]\n );\n\n const leftoverFields = React.useMemo(() => {\n const known = new Set([...Object.keys(fieldRules), ...identityKeys]);\n return Object.keys(record)\n .filter((k) => !known.has(k))\n .map((k) => resolveField(k, record[k], inferRule(k, record[k]), { isLeftover: true, classifySignal, toneMap }));\n }, [record, fieldRules, identityKeys, classifySignal, toneMap]);\n\n const bannerContent = banner ? banner(record) : null;\n\n return (\n <div\n style={{\n background: theme.colors.surface,\n border: `1px solid ${theme.colors.border}`,\n borderRadius: theme.radii.lg,\n padding: '36px 40px',\n display: 'flex',\n flexDirection: 'column',\n gap: 26,\n fontFamily: theme.fonts.body,\n color: theme.colors.textPrimary,\n ...style,\n }}\n >\n {header?.(record)}\n\n {bannerContent && (\n <div\n style={{\n background: `${theme.tonePalette.rose ?? '#E2564F'}14`,\n border: `1px solid ${theme.tonePalette.rose ?? '#E2564F'}48`,\n borderRadius: 10,\n padding: '16px 18px',\n display: 'flex',\n gap: 12,\n alignItems: 'flex-start',\n }}\n >\n <WarningIcon size={19} color={theme.tonePalette.rose ?? '#E2564F'} style={{ flexShrink: 0, marginTop: 2 }} />\n <div style={{ display: 'flex', flexDirection: 'column', gap: 7 }}>\n <span style={{ fontFamily: theme.fonts.heading, fontSize: 14, fontWeight: 600, color: theme.colors.textPrimary }}>\n {bannerContent.title}\n </span>\n <span style={{ fontSize: 13.5, lineHeight: 1.55, color: theme.colors.textSecondary }}>{bannerContent.body}</span>\n {bannerContent.tags && bannerContent.tags.length > 0 && (\n <div style={{ display: 'flex', gap: 6, marginTop: 2, flexWrap: 'wrap' }}>\n {bannerContent.tags.map((t, i) => (\n <span\n key={i}\n style={{\n background: `${theme.tonePalette.rose ?? '#E2564F'}24`,\n color: theme.tonePalette.rose ?? '#E2564F',\n border: `1px solid ${theme.tonePalette.rose ?? '#E2564F'}55`,\n padding: '3px 8px',\n borderRadius: 5,\n fontFamily: theme.fonts.heading,\n fontSize: 10.5,\n fontWeight: 600,\n letterSpacing: '0.04em',\n textTransform: 'uppercase',\n }}\n >\n {t}\n </span>\n ))}\n </div>\n )}\n </div>\n </div>\n )}\n\n {contentFields.map((field) => (\n <FieldRenderer key={field.key} field={field} variant=\"content\" />\n ))}\n\n {(diagnosticFields.length > 0 || leftoverFields.length > 0) && (\n <>\n <div style={{ height: 1, background: theme.colors.border }} />\n\n <button\n onClick={() => setOpen((v) => !v)}\n style={{\n all: 'unset',\n cursor: 'pointer',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n width: '100%',\n boxSizing: 'border-box',\n }}\n >\n <div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>\n <span style={{ fontFamily: theme.fonts.heading, fontSize: 12.5, fontWeight: 600, letterSpacing: '0.04em', color: theme.colors.textSecondary }}>\n {diagnosticsLabel}\n </span>\n <span style={{ fontSize: 11.5, color: theme.colors.textMuted }}>{diagnosticsHint}</span>\n </div>\n <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>\n <span style={{ fontSize: 12, color: theme.colors.textMuted }}>{open ? 'Hide' : 'Show'}</span>\n <ChevronDownIcon\n size={14}\n color={theme.colors.textMuted}\n style={{ transform: open ? 'rotate(180deg)' : 'rotate(0deg)', transition: 'transform 120ms ease' }}\n />\n </div>\n </button>\n\n {open && (\n <div\n style={{\n background: theme.colors.surfaceMuted,\n border: `1px solid ${theme.colors.border}`,\n borderRadius: theme.radii.md,\n padding: '20px 22px',\n display: 'flex',\n flexDirection: 'column',\n gap: 18,\n }}\n >\n {diagnosticFields.length > 0 && (\n <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>\n {diagnosticFields.map((field) => (\n <FieldRenderer key={field.key} field={field} variant=\"compact\" />\n ))}\n </div>\n )}\n\n {leftoverFields.length > 0 && (\n <>\n <div style={{ height: 1, borderTop: `1px dashed ${theme.colors.border}` }} />\n <div style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>\n <span\n style={{\n fontFamily: theme.fonts.heading,\n fontSize: 10.5,\n fontWeight: 600,\n letterSpacing: '0.08em',\n textTransform: 'uppercase',\n color: theme.colors.textMuted,\n }}\n >\n Additional Fields\n </span>\n <span style={{ fontSize: 11, color: theme.colors.textMuted, opacity: 0.7 }}>\n Not in fieldRules yet — rendered generically from each value's shape\n </span>\n </div>\n <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>\n {leftoverFields.map((field) => (\n <FieldRenderer key={field.key} field={field} variant=\"compact\" />\n ))}\n </div>\n </>\n )}\n </div>\n )}\n </>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":["base","base","import_jsx_runtime","import_jsx_runtime","React","import_jsx_runtime","_a","_b","_c"]}
@@ -0,0 +1,186 @@
1
+ import * as React from 'react';
2
+
3
+ /** Named tones the palette guarantees. A theme can rename their hexes; consumer
4
+ * toneMaps point AT these names (e.g. { REQUIRE_INFORMATION: 'amber' }), never at hex. */
5
+ declare const TONE_PALETTE: {
6
+ readonly neutral: "#8B96A3";
7
+ readonly amber: "#E8A33D";
8
+ readonly rose: "#E2564F";
9
+ readonly green: "#59B87B";
10
+ readonly blue: "#6FA8DC";
11
+ readonly violet: "#9B8CDB";
12
+ readonly teal: "#4FB6A8";
13
+ };
14
+ type ToneName = keyof typeof TONE_PALETTE;
15
+ /** Deterministic fallback so an enum value nobody mapped yet still gets a stable,
16
+ * distinct color instead of falling back to the same neutral gray every time. */
17
+ declare function hashTone(value: string): ToneName;
18
+ /** `colors.accent` and every value in `tonePalette` must be 6-digit hex ('#rrggbb'):
19
+ * FieldRenderer and RecordView tint them for badges/boxes by appending 2 hex digits
20
+ * of alpha ('${hex}24'), which only produces valid CSS for that format. The rest of
21
+ * `colors` is used as-is and accepts any valid CSS color. */
22
+ interface FieldKitTheme {
23
+ colors: {
24
+ background: string;
25
+ surface: string;
26
+ surfaceMuted: string;
27
+ border: string;
28
+ textPrimary: string;
29
+ textSecondary: string;
30
+ textMuted: string;
31
+ accent: string;
32
+ };
33
+ fonts: {
34
+ heading: string;
35
+ body: string;
36
+ mono: string;
37
+ };
38
+ radii: {
39
+ sm: string;
40
+ md: string;
41
+ lg: string;
42
+ pill: string;
43
+ };
44
+ tonePalette: Record<string, string>;
45
+ }
46
+ declare const defaultTheme: FieldKitTheme;
47
+ type FieldKitThemeOverride = {
48
+ [K in keyof FieldKitTheme]?: Partial<FieldKitTheme[K]>;
49
+ };
50
+ interface FieldKitProviderProps {
51
+ theme?: FieldKitThemeOverride;
52
+ children: React.ReactNode;
53
+ }
54
+ /** Wrap your app (or just the part that uses fieldkit) in this to change colors,
55
+ * fonts, or radii. Anything you don't override keeps the default. */
56
+ declare function FieldKitProvider({ theme, children }: FieldKitProviderProps): React.JSX.Element;
57
+ declare function useFieldKitTheme(): FieldKitTheme;
58
+ declare function toneHex(theme: FieldKitTheme, toneKey: string | undefined): string;
59
+
60
+ /** Every shape the toolbox knows how to paint. Adding one here means touching
61
+ * exactly one switch (FieldRenderer) — TypeScript's exhaustiveness check is
62
+ * what keeps a new kind from ever silently rendering nothing. */
63
+ type FieldKind = 'lead' | 'bullets' | 'signals' | 'qa' | 'pill' | 'mono' | 'text' | 'flag' | 'stat' | 'tags' | 'empty' | 'json';
64
+ /** One entry in the metadata map you own: how a field named `key` should render. */
65
+ interface FieldRule {
66
+ /** Display label. Defaults to a title-cased version of the key. */
67
+ label?: string;
68
+ as: FieldKind;
69
+ }
70
+ type FieldRules = Record<string, FieldRule>;
71
+ interface ResolvedItem {
72
+ text: string;
73
+ warn?: boolean;
74
+ info?: boolean;
75
+ }
76
+ /** A field, resolved into exactly what its FieldRenderer branch needs — nothing more. */
77
+ interface ResolvedField {
78
+ key: string;
79
+ label: string;
80
+ kind: FieldKind;
81
+ /** True when this field reached its kind via inferRule() rather than a rule you wrote. */
82
+ isLeftover: boolean;
83
+ text?: string;
84
+ value?: string;
85
+ items?: ResolvedItem[];
86
+ toneKey?: string;
87
+ pillText?: string;
88
+ flagOn?: boolean;
89
+ flagText?: string;
90
+ }
91
+ interface BannerContent {
92
+ title: string;
93
+ body: string;
94
+ tags?: string[];
95
+ }
96
+ /** Classifies one array-item string in a 'signals' field as a warning or a plain note. */
97
+ type ClassifySignal = (text: string) => 'warn' | 'info';
98
+ /** Maps a raw enum-like value (e.g. "REQUIRE_INFORMATION") to a tone key (e.g. "amber").
99
+ * Domain-specific — you own this, the package only owns the palette + a hash fallback. */
100
+ type ToneMap = Record<string, string>;
101
+
102
+ /** camelCase / snake_case -> "Title Case", used both for auto-labeling leftover
103
+ * fields and for humanizing enum values ("REQUIRE_INFORMATION" -> "Require Information"). */
104
+ declare function labelFromKey(key: string): string;
105
+ /** Default severity heuristic for a 'signals' field: sentences that assert something
106
+ * is unresolved/unknown/missing read as a warning; everything else as a plain note.
107
+ * Override via RecordView's `classifySignal` prop if your domain needs sharper rules. */
108
+ declare const defaultClassifySignal: ClassifySignal;
109
+ /** The leftover handler: infers a render kind from the VALUE's shape alone, for any
110
+ * key your FieldRules map doesn't (yet) cover. Same kinds as the curated map — this
111
+ * is a safety net, not a second taxonomy. */
112
+ declare function inferRule(key: string, value: unknown): FieldRule;
113
+
114
+ interface ResolveOptions {
115
+ isLeftover?: boolean;
116
+ classifySignal?: ClassifySignal;
117
+ toneMap?: ToneMap;
118
+ }
119
+ /** Turns one (key, value, rule) triple into everything FieldRenderer needs — the one
120
+ * place that understands what each FieldKind's payload looks like. Used identically
121
+ * for curated fields (rule from your FieldRules map) and leftovers (rule from inferRule). */
122
+ declare function resolveField(key: string, value: unknown, rule: FieldRule, options?: ResolveOptions): ResolvedField;
123
+
124
+ interface FieldRendererProps {
125
+ field: ResolvedField;
126
+ /** 'content' = the full-weight treatment (bigger text, icons, tinted boxes) for
127
+ * primary content. 'compact' = the label-over-value treatment used for diagnostics
128
+ * and leftovers. Both cover all twelve kinds — nothing renders blank because of
129
+ * which tier a field ended up in. */
130
+ variant?: 'content' | 'compact';
131
+ /** Renders the field's label above its value. RecordView turns this off when it
132
+ * wants to draw the label itself (e.g. inside a shared row layout). */
133
+ showLabel?: boolean;
134
+ }
135
+ /** Renders exactly one resolved field. This is the only place that knows what each
136
+ * FieldKind looks like — every tier (content / diagnostics / leftovers) calls the
137
+ * same component, so a kind can never be supported in one place and forgotten in
138
+ * another the way three separate hand-written blocks could silently drift apart. */
139
+ declare function FieldRenderer({ field, variant, showLabel }: FieldRendererProps): React.JSX.Element;
140
+
141
+ interface RecordViewProps {
142
+ /** The record to render. Any shape — only `fieldRules` + the key lists below decide meaning. */
143
+ record: Record<string, unknown>;
144
+ /** Your metadata map: field name -> how to render it. Extend this whenever you want a
145
+ * field promoted out of the leftover bucket. */
146
+ fieldRules: FieldRules;
147
+ /** Keys rendered as primary content, in order, using the full-weight FieldRenderer variant. */
148
+ contentKeys: string[];
149
+ /** Keys rendered inside the collapsible diagnostics panel, using the compact variant. */
150
+ diagnosticKeys: string[];
151
+ /** Keys handled entirely by your own `header` render prop — excluded from generic
152
+ * rendering (title, status, timestamps: whatever your header needs). */
153
+ identityKeys?: string[];
154
+ /** Renders the card's header (title, status pill, meta row, …). Domain-specific by
155
+ * nature, so fieldkit doesn't guess at it. */
156
+ header?: (record: Record<string, unknown>) => React.ReactNode;
157
+ /** Derives an optional attention banner from the record — e.g. promote a broken
158
+ * pipeline state to the top instead of leaving it buried in diagnostics. */
159
+ banner?: (record: Record<string, unknown>) => BannerContent | null;
160
+ /** Overrides the default warn/info heuristic used by 'signals' fields. */
161
+ classifySignal?: ClassifySignal;
162
+ /** Maps your domain's enum values (statuses, quality flags, …) to tone names. Anything
163
+ * not listed here still gets a stable color via a deterministic hash, never a crash. */
164
+ toneMap?: ToneMap;
165
+ defaultDiagnosticsOpen?: boolean;
166
+ diagnosticsLabel?: string;
167
+ diagnosticsHint?: string;
168
+ style?: React.CSSProperties;
169
+ }
170
+ /** Renders a record's content fields, plus a collapsible panel holding both the
171
+ * curated diagnostic fields AND anything left over — every key in `record` ends up
172
+ * somewhere, never silently dropped. */
173
+ declare function RecordView({ record, fieldRules, contentKeys, diagnosticKeys, identityKeys, header, banner, classifySignal, toneMap, defaultDiagnosticsOpen, diagnosticsLabel, diagnosticsHint, style, }: RecordViewProps): React.JSX.Element;
174
+
175
+ interface IconProps {
176
+ size?: number;
177
+ color?: string;
178
+ style?: React.CSSProperties;
179
+ }
180
+ declare function CheckIcon({ size, color, style }: IconProps): React.JSX.Element;
181
+ declare function WarningIcon({ size, color, style }: IconProps): React.JSX.Element;
182
+ declare function InfoIcon({ size, color, style }: IconProps): React.JSX.Element;
183
+ declare function QuestionIcon({ size, color, style }: IconProps): React.JSX.Element;
184
+ declare function ChevronDownIcon({ size, color, style }: IconProps): React.JSX.Element;
185
+
186
+ export { type BannerContent, CheckIcon, ChevronDownIcon, type ClassifySignal, type FieldKind, FieldKitProvider, type FieldKitTheme, type FieldKitThemeOverride, FieldRenderer, type FieldRendererProps, type FieldRule, type FieldRules, type IconProps, InfoIcon, QuestionIcon, RecordView, type RecordViewProps, type ResolveOptions, type ResolvedField, type ResolvedItem, TONE_PALETTE, type ToneMap, type ToneName, WarningIcon, defaultClassifySignal, defaultTheme, hashTone, inferRule, labelFromKey, resolveField, toneHex, useFieldKitTheme };
@@ -0,0 +1,186 @@
1
+ import * as React from 'react';
2
+
3
+ /** Named tones the palette guarantees. A theme can rename their hexes; consumer
4
+ * toneMaps point AT these names (e.g. { REQUIRE_INFORMATION: 'amber' }), never at hex. */
5
+ declare const TONE_PALETTE: {
6
+ readonly neutral: "#8B96A3";
7
+ readonly amber: "#E8A33D";
8
+ readonly rose: "#E2564F";
9
+ readonly green: "#59B87B";
10
+ readonly blue: "#6FA8DC";
11
+ readonly violet: "#9B8CDB";
12
+ readonly teal: "#4FB6A8";
13
+ };
14
+ type ToneName = keyof typeof TONE_PALETTE;
15
+ /** Deterministic fallback so an enum value nobody mapped yet still gets a stable,
16
+ * distinct color instead of falling back to the same neutral gray every time. */
17
+ declare function hashTone(value: string): ToneName;
18
+ /** `colors.accent` and every value in `tonePalette` must be 6-digit hex ('#rrggbb'):
19
+ * FieldRenderer and RecordView tint them for badges/boxes by appending 2 hex digits
20
+ * of alpha ('${hex}24'), which only produces valid CSS for that format. The rest of
21
+ * `colors` is used as-is and accepts any valid CSS color. */
22
+ interface FieldKitTheme {
23
+ colors: {
24
+ background: string;
25
+ surface: string;
26
+ surfaceMuted: string;
27
+ border: string;
28
+ textPrimary: string;
29
+ textSecondary: string;
30
+ textMuted: string;
31
+ accent: string;
32
+ };
33
+ fonts: {
34
+ heading: string;
35
+ body: string;
36
+ mono: string;
37
+ };
38
+ radii: {
39
+ sm: string;
40
+ md: string;
41
+ lg: string;
42
+ pill: string;
43
+ };
44
+ tonePalette: Record<string, string>;
45
+ }
46
+ declare const defaultTheme: FieldKitTheme;
47
+ type FieldKitThemeOverride = {
48
+ [K in keyof FieldKitTheme]?: Partial<FieldKitTheme[K]>;
49
+ };
50
+ interface FieldKitProviderProps {
51
+ theme?: FieldKitThemeOverride;
52
+ children: React.ReactNode;
53
+ }
54
+ /** Wrap your app (or just the part that uses fieldkit) in this to change colors,
55
+ * fonts, or radii. Anything you don't override keeps the default. */
56
+ declare function FieldKitProvider({ theme, children }: FieldKitProviderProps): React.JSX.Element;
57
+ declare function useFieldKitTheme(): FieldKitTheme;
58
+ declare function toneHex(theme: FieldKitTheme, toneKey: string | undefined): string;
59
+
60
+ /** Every shape the toolbox knows how to paint. Adding one here means touching
61
+ * exactly one switch (FieldRenderer) — TypeScript's exhaustiveness check is
62
+ * what keeps a new kind from ever silently rendering nothing. */
63
+ type FieldKind = 'lead' | 'bullets' | 'signals' | 'qa' | 'pill' | 'mono' | 'text' | 'flag' | 'stat' | 'tags' | 'empty' | 'json';
64
+ /** One entry in the metadata map you own: how a field named `key` should render. */
65
+ interface FieldRule {
66
+ /** Display label. Defaults to a title-cased version of the key. */
67
+ label?: string;
68
+ as: FieldKind;
69
+ }
70
+ type FieldRules = Record<string, FieldRule>;
71
+ interface ResolvedItem {
72
+ text: string;
73
+ warn?: boolean;
74
+ info?: boolean;
75
+ }
76
+ /** A field, resolved into exactly what its FieldRenderer branch needs — nothing more. */
77
+ interface ResolvedField {
78
+ key: string;
79
+ label: string;
80
+ kind: FieldKind;
81
+ /** True when this field reached its kind via inferRule() rather than a rule you wrote. */
82
+ isLeftover: boolean;
83
+ text?: string;
84
+ value?: string;
85
+ items?: ResolvedItem[];
86
+ toneKey?: string;
87
+ pillText?: string;
88
+ flagOn?: boolean;
89
+ flagText?: string;
90
+ }
91
+ interface BannerContent {
92
+ title: string;
93
+ body: string;
94
+ tags?: string[];
95
+ }
96
+ /** Classifies one array-item string in a 'signals' field as a warning or a plain note. */
97
+ type ClassifySignal = (text: string) => 'warn' | 'info';
98
+ /** Maps a raw enum-like value (e.g. "REQUIRE_INFORMATION") to a tone key (e.g. "amber").
99
+ * Domain-specific — you own this, the package only owns the palette + a hash fallback. */
100
+ type ToneMap = Record<string, string>;
101
+
102
+ /** camelCase / snake_case -> "Title Case", used both for auto-labeling leftover
103
+ * fields and for humanizing enum values ("REQUIRE_INFORMATION" -> "Require Information"). */
104
+ declare function labelFromKey(key: string): string;
105
+ /** Default severity heuristic for a 'signals' field: sentences that assert something
106
+ * is unresolved/unknown/missing read as a warning; everything else as a plain note.
107
+ * Override via RecordView's `classifySignal` prop if your domain needs sharper rules. */
108
+ declare const defaultClassifySignal: ClassifySignal;
109
+ /** The leftover handler: infers a render kind from the VALUE's shape alone, for any
110
+ * key your FieldRules map doesn't (yet) cover. Same kinds as the curated map — this
111
+ * is a safety net, not a second taxonomy. */
112
+ declare function inferRule(key: string, value: unknown): FieldRule;
113
+
114
+ interface ResolveOptions {
115
+ isLeftover?: boolean;
116
+ classifySignal?: ClassifySignal;
117
+ toneMap?: ToneMap;
118
+ }
119
+ /** Turns one (key, value, rule) triple into everything FieldRenderer needs — the one
120
+ * place that understands what each FieldKind's payload looks like. Used identically
121
+ * for curated fields (rule from your FieldRules map) and leftovers (rule from inferRule). */
122
+ declare function resolveField(key: string, value: unknown, rule: FieldRule, options?: ResolveOptions): ResolvedField;
123
+
124
+ interface FieldRendererProps {
125
+ field: ResolvedField;
126
+ /** 'content' = the full-weight treatment (bigger text, icons, tinted boxes) for
127
+ * primary content. 'compact' = the label-over-value treatment used for diagnostics
128
+ * and leftovers. Both cover all twelve kinds — nothing renders blank because of
129
+ * which tier a field ended up in. */
130
+ variant?: 'content' | 'compact';
131
+ /** Renders the field's label above its value. RecordView turns this off when it
132
+ * wants to draw the label itself (e.g. inside a shared row layout). */
133
+ showLabel?: boolean;
134
+ }
135
+ /** Renders exactly one resolved field. This is the only place that knows what each
136
+ * FieldKind looks like — every tier (content / diagnostics / leftovers) calls the
137
+ * same component, so a kind can never be supported in one place and forgotten in
138
+ * another the way three separate hand-written blocks could silently drift apart. */
139
+ declare function FieldRenderer({ field, variant, showLabel }: FieldRendererProps): React.JSX.Element;
140
+
141
+ interface RecordViewProps {
142
+ /** The record to render. Any shape — only `fieldRules` + the key lists below decide meaning. */
143
+ record: Record<string, unknown>;
144
+ /** Your metadata map: field name -> how to render it. Extend this whenever you want a
145
+ * field promoted out of the leftover bucket. */
146
+ fieldRules: FieldRules;
147
+ /** Keys rendered as primary content, in order, using the full-weight FieldRenderer variant. */
148
+ contentKeys: string[];
149
+ /** Keys rendered inside the collapsible diagnostics panel, using the compact variant. */
150
+ diagnosticKeys: string[];
151
+ /** Keys handled entirely by your own `header` render prop — excluded from generic
152
+ * rendering (title, status, timestamps: whatever your header needs). */
153
+ identityKeys?: string[];
154
+ /** Renders the card's header (title, status pill, meta row, …). Domain-specific by
155
+ * nature, so fieldkit doesn't guess at it. */
156
+ header?: (record: Record<string, unknown>) => React.ReactNode;
157
+ /** Derives an optional attention banner from the record — e.g. promote a broken
158
+ * pipeline state to the top instead of leaving it buried in diagnostics. */
159
+ banner?: (record: Record<string, unknown>) => BannerContent | null;
160
+ /** Overrides the default warn/info heuristic used by 'signals' fields. */
161
+ classifySignal?: ClassifySignal;
162
+ /** Maps your domain's enum values (statuses, quality flags, …) to tone names. Anything
163
+ * not listed here still gets a stable color via a deterministic hash, never a crash. */
164
+ toneMap?: ToneMap;
165
+ defaultDiagnosticsOpen?: boolean;
166
+ diagnosticsLabel?: string;
167
+ diagnosticsHint?: string;
168
+ style?: React.CSSProperties;
169
+ }
170
+ /** Renders a record's content fields, plus a collapsible panel holding both the
171
+ * curated diagnostic fields AND anything left over — every key in `record` ends up
172
+ * somewhere, never silently dropped. */
173
+ declare function RecordView({ record, fieldRules, contentKeys, diagnosticKeys, identityKeys, header, banner, classifySignal, toneMap, defaultDiagnosticsOpen, diagnosticsLabel, diagnosticsHint, style, }: RecordViewProps): React.JSX.Element;
174
+
175
+ interface IconProps {
176
+ size?: number;
177
+ color?: string;
178
+ style?: React.CSSProperties;
179
+ }
180
+ declare function CheckIcon({ size, color, style }: IconProps): React.JSX.Element;
181
+ declare function WarningIcon({ size, color, style }: IconProps): React.JSX.Element;
182
+ declare function InfoIcon({ size, color, style }: IconProps): React.JSX.Element;
183
+ declare function QuestionIcon({ size, color, style }: IconProps): React.JSX.Element;
184
+ declare function ChevronDownIcon({ size, color, style }: IconProps): React.JSX.Element;
185
+
186
+ export { type BannerContent, CheckIcon, ChevronDownIcon, type ClassifySignal, type FieldKind, FieldKitProvider, type FieldKitTheme, type FieldKitThemeOverride, FieldRenderer, type FieldRendererProps, type FieldRule, type FieldRules, type IconProps, InfoIcon, QuestionIcon, RecordView, type RecordViewProps, type ResolveOptions, type ResolvedField, type ResolvedItem, TONE_PALETTE, type ToneMap, type ToneName, WarningIcon, defaultClassifySignal, defaultTheme, hashTone, inferRule, labelFromKey, resolveField, toneHex, useFieldKitTheme };