{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "formats", "title": "Value Formats", "description": "Common empty, text, number, currency, percent, bytes, duration, date, boolean, and phone value formatters", "dependencies": ["daisyui"], "files": [ { "path": "registry/default/ui/formats/format-values.ts", "content": "'use client';\n\nexport type FormatLocale = string | string[];\nexport type EmptyFormatValue = null | undefined | '';\nexport type NumberInput = number | string | bigint | EmptyFormatValue;\nexport type DateInput = Date | number | string | EmptyFormatValue;\nexport type BooleanInput = boolean | string | number | EmptyFormatValue;\n\nexport type FormatFallbackOptions = {\n\tfallback?: string;\n};\n\nexport type FormatNumberOptions = Intl.NumberFormatOptions &\n\tFormatFallbackOptions & {\n\t\tlocale?: FormatLocale;\n\t};\n\nexport type FormatCurrencyOptions = Omit & {\n\tcurrency?: string;\n};\n\nexport type FormatPercentOptions = Omit & {\n\tinput?: 'ratio' | 'percent';\n};\n\nexport type FormatBytesOptions = FormatFallbackOptions & {\n\t/** Defaults to 1000 for decimal labels and 1024 for binary labels. */\n\tbase?: 1000 | 1024;\n\tbinary?: boolean;\n\tmaximumFractionDigits?: number;\n\tminimumFractionDigits?: number;\n};\n\nexport type DurationInput =\n\t| number\n\t| string\n\t| EmptyFormatValue\n\t| Partial>\n\t| {\n\t\t\tvalue: number | string;\n\t\t\tunit?: DurationUnit;\n\t };\n\nexport type DurationUnit = 'millisecond' | 'second' | 'minute' | 'hour' | 'day';\nexport type DurationStyle = 'compact' | 'digital' | 'iso';\n\nexport type FormatDurationOptions = FormatFallbackOptions & {\n\tstyle?: DurationStyle;\n\tunit?: DurationUnit;\n\tmaxParts?: number;\n\tshowZero?: boolean;\n};\n\nexport type FormatDateTimeOptions = Intl.DateTimeFormatOptions &\n\tFormatFallbackOptions & {\n\t\tlocale?: FormatLocale;\n\t};\n\nexport type FormatRelativeTimeOptions = FormatFallbackOptions & {\n\tlocale?: FormatLocale;\n\tnow?: DateInput;\n\tnumeric?: Intl.RelativeTimeFormatNumeric;\n\tstyle?: Intl.RelativeTimeFormatStyle;\n};\n\nexport type FormatPhoneNumberOptions = FormatFallbackOptions & {\n\tmask?: boolean;\n\tseparator?: string;\n};\n\nconst defaultFallback = '—';\nconst durationUnitMs: Record = {\n\tmillisecond: 1,\n\tsecond: 1000,\n\tminute: 60_000,\n\thour: 3_600_000,\n\tday: 86_400_000,\n};\n\nexport function isEmptyFormatValue(value: unknown): value is EmptyFormatValue {\n\treturn value === null || value === undefined || value === '';\n}\n\nexport function parseFiniteNumber(value: NumberInput): number | undefined {\n\tif (isEmptyFormatValue(value)) return undefined;\n\tif (typeof value === 'bigint') {\n\t\tconst numberValue = Number(value);\n\t\treturn Number.isSafeInteger(numberValue) ? numberValue : undefined;\n\t}\n\tif (typeof value === 'number') return Number.isFinite(value) ? value : undefined;\n\tconst trimmed = value.trim();\n\tif (!trimmed) return undefined;\n\tconst parsed = Number(trimmed.replace(/,/g, ''));\n\treturn Number.isFinite(parsed) ? parsed : undefined;\n}\n\nexport function parseDateValue(value: DateInput): Date | undefined {\n\tif (isEmptyFormatValue(value)) return undefined;\n\tconst date = value instanceof Date ? value : new Date(value);\n\treturn Number.isFinite(date.getTime()) ? date : undefined;\n}\n\nexport function formatNumber(\n\tvalue: NumberInput,\n\t{ locale, fallback = defaultFallback, ...options }: FormatNumberOptions = {},\n) {\n\tconst numberValue = parseFiniteNumber(value);\n\tif (numberValue === undefined) return fallback;\n\treturn safeFormat(fallback, () => new Intl.NumberFormat(locale, options).format(numberValue));\n}\n\nexport function formatDecimal(\n\tvalue: NumberInput,\n\t{ minimumFractionDigits = 2, maximumFractionDigits = 2, ...options }: FormatNumberOptions = {},\n) {\n\treturn formatNumber(value, { minimumFractionDigits, maximumFractionDigits, ...options });\n}\n\nexport function formatCurrency(\n\tvalue: NumberInput,\n\t{ locale, currency = 'CNY', fallback = defaultFallback, ...options }: FormatCurrencyOptions = {},\n) {\n\tconst numberValue = parseFiniteNumber(value);\n\tif (numberValue === undefined) return fallback;\n\treturn safeFormat(fallback, () =>\n\t\tnew Intl.NumberFormat(locale, { currency, style: 'currency', ...options }).format(numberValue),\n\t);\n}\n\nexport function formatPercent(\n\tvalue: NumberInput,\n\t{\n\t\tlocale,\n\t\tinput = 'ratio',\n\t\tfallback = defaultFallback,\n\t\tmaximumFractionDigits = 2,\n\t\t...options\n\t}: FormatPercentOptions = {},\n) {\n\tconst numberValue = parseFiniteNumber(value);\n\tif (numberValue === undefined) return fallback;\n\treturn safeFormat(fallback, () =>\n\t\tnew Intl.NumberFormat(locale, {\n\t\t\tmaximumFractionDigits,\n\t\t\tstyle: 'percent',\n\t\t\t...options,\n\t\t}).format(input === 'percent' ? numberValue / 100 : numberValue),\n\t);\n}\n\nexport function formatBytes(\n\tvalue: NumberInput,\n\t{\n\t\tbase,\n\t\tbinary = false,\n\t\tfallback = defaultFallback,\n\t\tmaximumFractionDigits = 2,\n\t\tminimumFractionDigits = 0,\n\t}: FormatBytesOptions = {},\n) {\n\tconst numberValue = parseFiniteNumber(value);\n\tif (numberValue === undefined) return fallback;\n\tif (numberValue === 0) return '0 B';\n\tconst sign = numberValue < 0 ? '-' : '';\n\tconst absolute = Math.abs(numberValue);\n\tconst scaleBase = base ?? (binary ? 1024 : 1000);\n\tconst units = binary ? ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB'] : ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];\n\tconst unitIndex = Math.min(units.length - 1, Math.floor(Math.log(absolute) / Math.log(scaleBase)));\n\tconst scaled = absolute / scaleBase ** unitIndex;\n\tconst formatted = safeFormat(fallback, () =>\n\t\tnew Intl.NumberFormat('en-US', {\n\t\t\tmaximumFractionDigits: unitIndex === 0 ? 0 : maximumFractionDigits,\n\t\t\tminimumFractionDigits: unitIndex === 0 ? 0 : minimumFractionDigits,\n\t\t}).format(scaled),\n\t);\n\treturn formatted === fallback ? fallback : `${sign}${formatted} ${units[unitIndex]}`;\n}\n\nexport function parseDurationMilliseconds(value: DurationInput, defaultUnit: DurationUnit = 'millisecond') {\n\tif (isEmptyFormatValue(value)) return undefined;\n\tif (typeof value === 'number') return value * durationUnitMs[defaultUnit];\n\tif (typeof value === 'string') {\n\t\tconst numberValue = parseFiniteNumber(value);\n\t\tif (numberValue !== undefined) return numberValue * durationUnitMs[defaultUnit];\n\t\treturn parseIsoDurationMilliseconds(value);\n\t}\n\tif ('value' in value) {\n\t\tconst numberValue = parseFiniteNumber(value.value);\n\t\treturn numberValue === undefined ? undefined : numberValue * durationUnitMs[value.unit ?? defaultUnit];\n\t}\n\tlet total = 0;\n\tfor (const [unit, amount] of Object.entries(value) as Array<\n\t\t['days' | 'hours' | 'minutes' | 'seconds' | 'milliseconds', number | undefined]\n\t>) {\n\t\tif (amount === undefined) continue;\n\t\tif (!Number.isFinite(amount)) return undefined;\n\t\tconst normalized = unit.slice(0, -1) as DurationUnit;\n\t\ttotal += amount * durationUnitMs[normalized];\n\t}\n\treturn total;\n}\n\nexport function formatDuration(\n\tvalue: DurationInput,\n\t{\n\t\tstyle = 'compact',\n\t\tunit = 'millisecond',\n\t\tfallback = defaultFallback,\n\t\tmaxParts = 2,\n\t\tshowZero = true,\n\t}: FormatDurationOptions = {},\n) {\n\tconst milliseconds = parseDurationMilliseconds(value, unit);\n\tif (milliseconds === undefined) return fallback;\n\tconst sign = milliseconds < 0 ? '-' : '';\n\tlet remaining = Math.abs(milliseconds);\n\tif (style === 'iso') return `${sign}${toIsoDuration(remaining)}`;\n\tif (style === 'digital') return `${sign}${toDigitalDuration(remaining)}`;\n\n\tconst parts: string[] = [];\n\tconst units: Array<[label: string, size: number]> = [\n\t\t['d', durationUnitMs.day],\n\t\t['h', durationUnitMs.hour],\n\t\t['m', durationUnitMs.minute],\n\t\t['s', durationUnitMs.second],\n\t\t['ms', durationUnitMs.millisecond],\n\t];\n\tfor (const [label, size] of units) {\n\t\tconst amount = Math.floor(remaining / size);\n\t\tif (amount > 0) {\n\t\t\tparts.push(`${amount}${label}`);\n\t\t\tremaining -= amount * size;\n\t\t}\n\t\tif (parts.length >= maxParts) break;\n\t}\n\tif (parts.length === 0 && showZero) return '0ms';\n\treturn `${sign}${parts.join('') || fallback}`;\n}\n\nexport function formatDateTime(\n\tvalue: DateInput,\n\t{ locale, fallback = defaultFallback, ...options }: FormatDateTimeOptions = {},\n) {\n\tconst date = parseDateValue(value);\n\tif (!date) return fallback;\n\treturn safeFormat(fallback, () => new Intl.DateTimeFormat(locale, options).format(date));\n}\n\nexport function formatDate(value: DateInput, options: FormatDateTimeOptions = {}) {\n\treturn formatDateTime(value, { dateStyle: 'medium', ...options });\n}\n\nexport function formatTime(value: DateInput, options: FormatDateTimeOptions = {}) {\n\treturn formatDateTime(value, { timeStyle: 'short', ...options });\n}\n\nexport function formatRelativeTime(\n\tvalue: DateInput,\n\t{\n\t\tlocale,\n\t\tnow = Date.now(),\n\t\tnumeric = 'auto',\n\t\tstyle = 'short',\n\t\tfallback = defaultFallback,\n\t}: FormatRelativeTimeOptions = {},\n) {\n\tconst date = parseDateValue(value);\n\tconst nowDate = parseDateValue(now);\n\tif (!date || !nowDate) return fallback;\n\tconst diffMs = date.getTime() - nowDate.getTime();\n\tconst units: Array<[Intl.RelativeTimeFormatUnit, number]> = [\n\t\t['year', 31_536_000_000],\n\t\t['month', 2_592_000_000],\n\t\t['week', 604_800_000],\n\t\t['day', 86_400_000],\n\t\t['hour', 3_600_000],\n\t\t['minute', 60_000],\n\t\t['second', 1000],\n\t];\n\tconst [relativeUnit, unitMs] = units.find(([, size]) => Math.abs(diffMs) >= size) ?? ['second', 1000];\n\tconst amount = Math.round(diffMs / unitMs);\n\treturn safeFormat(fallback, () =>\n\t\tnew Intl.RelativeTimeFormat(locale, { numeric, style }).format(amount, relativeUnit),\n\t);\n}\n\nexport function getRelativeTimeUpdateInterval(value: DateInput, now: number = Date.now()) {\n\tconst date = parseDateValue(value);\n\tif (!date) return 0;\n\tconst distance = Math.abs(date.getTime() - now);\n\tif (distance < 60_000) return 1000;\n\tif (distance < 3_600_000) return 60_000;\n\tif (distance < 86_400_000) return 300_000;\n\treturn 1_800_000;\n}\n\nexport function formatBoolean(\n\tvalue: BooleanInput,\n\t{\n\t\ttrueText = '是',\n\t\tfalseText = '否',\n\t\tfallback = defaultFallback,\n\t}: FormatFallbackOptions & { trueText?: string; falseText?: string } = {},\n) {\n\tif (isEmptyFormatValue(value)) return fallback;\n\tif (typeof value === 'boolean') return value ? trueText : falseText;\n\tif (typeof value === 'number') return value === 0 ? falseText : trueText;\n\tconst normalized = value.trim().toLowerCase();\n\tif (!normalized) return fallback;\n\tif (['true', '1', 'yes', 'y', 'on'].includes(normalized)) return trueText;\n\tif (['false', '0', 'no', 'n', 'off'].includes(normalized)) return falseText;\n\treturn fallback;\n}\n\nexport function formatPhoneNumber(\n\tvalue: string | EmptyFormatValue,\n\t{ mask = true, separator = ' ', fallback = defaultFallback }: FormatPhoneNumberOptions = {},\n) {\n\tif (isEmptyFormatValue(value)) return fallback;\n\tconst compact = value.replace(/\\D/g, '');\n\tif (!compact) return fallback;\n\tif (compact.length === 11) {\n\t\tconst head = compact.slice(0, 3);\n\t\tconst middle = mask ? '****' : compact.slice(3, 7);\n\t\tconst tail = compact.slice(7);\n\t\treturn [head, middle, tail].join(separator);\n\t}\n\tif (!mask) return compact;\n\tif (compact.length <= 7) return compact;\n\treturn `${compact.slice(0, 3)}${'*'.repeat(Math.max(3, compact.length - 7))}${compact.slice(-4)}`;\n}\n\nfunction parseIsoDurationMilliseconds(value: string) {\n\tconst match = value\n\t\t.trim()\n\t\t.match(/^(-)?P(?:(\\d+(?:\\.\\d+)?)D)?(?:T(?:(\\d+(?:\\.\\d+)?)H)?(?:(\\d+(?:\\.\\d+)?)M)?(?:(\\d+(?:\\.\\d+)?)S)?)?$/i);\n\tif (!match) return undefined;\n\tconst [, negative, days = '0', hours = '0', minutes = '0', seconds = '0'] = match;\n\tconst total =\n\t\tNumber(days) * durationUnitMs.day +\n\t\tNumber(hours) * durationUnitMs.hour +\n\t\tNumber(minutes) * durationUnitMs.minute +\n\t\tNumber(seconds) * durationUnitMs.second;\n\treturn negative ? -total : total;\n}\n\nfunction safeFormat(fallback: string, callback: () => string) {\n\ttry {\n\t\treturn callback();\n\t} catch {\n\t\treturn fallback;\n\t}\n}\n\nfunction toDigitalDuration(milliseconds: number) {\n\tconst totalSeconds = Math.floor(milliseconds / 1000);\n\tconst ms = Math.floor(milliseconds % 1000);\n\tconst seconds = totalSeconds % 60;\n\tconst totalMinutes = Math.floor(totalSeconds / 60);\n\tconst minutes = totalMinutes % 60;\n\tconst hours = Math.floor(totalMinutes / 60);\n\tconst main = hours > 0 ? `${hours}:${pad2(minutes)}:${pad2(seconds)}` : `${minutes}:${pad2(seconds)}`;\n\treturn ms > 0 ? `${main}.${String(ms).padStart(3, '0')}` : main;\n}\n\nfunction toIsoDuration(milliseconds: number) {\n\tlet remaining = Math.floor(milliseconds);\n\tconst days = Math.floor(remaining / durationUnitMs.day);\n\tremaining -= days * durationUnitMs.day;\n\tconst hours = Math.floor(remaining / durationUnitMs.hour);\n\tremaining -= hours * durationUnitMs.hour;\n\tconst minutes = Math.floor(remaining / durationUnitMs.minute);\n\tremaining -= minutes * durationUnitMs.minute;\n\tconst seconds = Math.floor(remaining / durationUnitMs.second);\n\tremaining -= seconds * durationUnitMs.second;\n\tconst secondText = remaining > 0 ? `${seconds}.${String(remaining).padStart(3, '0')}S` : `${seconds}S`;\n\tconst datePart = days > 0 ? `${days}D` : '';\n\tconst timePart =\n\t\thours || minutes || seconds || remaining\n\t\t\t? `T${hours ? `${hours}H` : ''}${minutes ? `${minutes}M` : ''}${secondText}`\n\t\t\t: '';\n\treturn `P${datePart}${timePart || 'T0S'}`;\n}\n\nfunction pad2(value: number) {\n\treturn String(value).padStart(2, '0');\n}\n", "type": "registry:lib", "target": "@components/formats/format-values.ts" }, { "path": "registry/default/ui/formats/format-components.tsx", "content": "'use client';\n\nimport {\n\ttype ComponentPropsWithRef,\n\tcreateContext,\n\ttype ElementType,\n\ttype ReactNode,\n\tuseContext,\n\tuseEffect,\n\tuseMemo,\n\tuseState,\n} from 'react';\nimport {\n\ttype BooleanInput,\n\ttype DateInput,\n\ttype DurationInput,\n\ttype DurationUnit,\n\ttype FormatBytesOptions,\n\ttype FormatLocale,\n\ttype FormatPhoneNumberOptions,\n\tformatBoolean,\n\tformatBytes,\n\tformatCurrency,\n\tformatDateTime,\n\tformatDecimal,\n\tformatDuration,\n\tformatNumber,\n\tformatPercent,\n\tformatPhoneNumber,\n\tformatRelativeTime,\n\tgetRelativeTimeUpdateInterval,\n\ttype NumberInput,\n\tparseDateValue,\n} from './format-values';\n\nexport type FormatConfig = {\n\tcurrency?: string;\n\tfallback?: string;\n\tlocale?: FormatLocale;\n\tplaceholder?: ReactNode;\n\ttimeZone?: string;\n};\n\nexport type FormatProviderProps = {\n\tchildren?: ReactNode;\n\tvalue?: FormatConfig;\n};\n\nexport type EmptyPlaceholderProps = ComponentPropsWithRef<'span'> & {\n\tas?: ElementType;\n};\n\nexport type TruncateFormatProps = ComponentPropsWithRef<'span'> & {\n\tas?: ElementType;\n\tplaceholder?: ReactNode;\n\tvalue?: ReactNode;\n};\n\nexport type ValueFormatProps = Omit, 'children'> & {\n\tplaceholder?: ReactNode;\n};\n\nexport type NumberFormatProps = ValueFormatProps & {\n\tfallback?: string;\n\tformatOptions?: Intl.NumberFormatOptions;\n\tlocale?: FormatLocale;\n\tvalue?: NumberInput;\n};\n\nexport type DecimalFormatProps = NumberFormatProps;\n\nexport type CurrencyFormatProps = ValueFormatProps & {\n\tcurrency?: string;\n\tfallback?: string;\n\tformatOptions?: Intl.NumberFormatOptions;\n\tlocale?: FormatLocale;\n\tvalue?: NumberInput;\n};\n\nexport type PercentFormatProps = ValueFormatProps & {\n\tfallback?: string;\n\tformatOptions?: Intl.NumberFormatOptions;\n\tinput?: 'ratio' | 'percent';\n\tlocale?: FormatLocale;\n\tvalue?: NumberInput;\n};\n\nexport type BytesFormatProps = ValueFormatProps &\n\tFormatBytesOptions & {\n\t\tvalue?: NumberInput;\n\t};\n\nexport type DurationFormatProps = ValueFormatProps & {\n\tdurationStyle?: 'compact' | 'digital' | 'iso';\n\tfallback?: string;\n\tmaxParts?: number;\n\tshowZero?: boolean;\n\tunit?: DurationUnit;\n\tvalue?: DurationInput;\n};\n\nexport type DateTimeFormatProps = Omit, 'dateTime'> & {\n\tfallback?: string;\n\tformatOptions?: Intl.DateTimeFormatOptions;\n\tlive?: boolean;\n\tlocale?: FormatLocale;\n\tplaceholder?: ReactNode;\n\trelative?: boolean;\n\ttimeZone?: string;\n\ttooltip?: boolean;\n\tvalue?: DateInput;\n};\n\nexport type DateFormatProps = DateTimeFormatProps;\nexport type TimeFormatProps = DateTimeFormatProps;\n\nexport type RelativeTimeFormatProps = Omit, 'dateTime'> & {\n\tlive?: boolean;\n\tlocale?: FormatLocale;\n\tnow?: DateInput;\n\tplaceholder?: ReactNode;\n\ttitleOptions?: Intl.DateTimeFormatOptions;\n\tvalue?: DateInput;\n};\n\nexport type BooleanFormatProps = ValueFormatProps & {\n\tbadge?: boolean;\n\tfallback?: string;\n\tfalseText?: string;\n\ttrueText?: string;\n\tvalue?: BooleanInput;\n};\n\nexport type PhoneNumberFormatProps = ValueFormatProps &\n\tFormatPhoneNumberOptions & {\n\t\tvalue?: string | null;\n\t};\n\nconst FormatConfigContext = createContext({});\n\nexport function FormatProvider({ children, value }: FormatProviderProps) {\n\tconst parent = useFormatConfig();\n\tconst next = useMemo(() => ({ ...parent, ...value }), [parent, value]);\n\treturn {children};\n}\n\nexport function useFormatConfig() {\n\treturn useContext(FormatConfigContext);\n}\n\nexport function EmptyPlaceholder({ as: Component = 'span', children, className, ...props }: EmptyPlaceholderProps) {\n\tconst config = useFormatConfig();\n\treturn (\n\t\t\n\t\t\t{children ?? config.placeholder ?? config.fallback ?? '—'}\n\t\t\n\t);\n}\n\nexport function TruncateFormat({\n\tas: Component = 'span',\n\tchildren,\n\tclassName,\n\tplaceholder,\n\ttitle,\n\tvalue,\n\t...props\n}: TruncateFormatProps) {\n\tconst content = children ?? value;\n\tif (isEmptyRenderable(content)) return <>{resolvePlaceholder(placeholder)};\n\tconst textTitle = title ?? (typeof content === 'string' || typeof content === 'number' ? String(content) : undefined);\n\treturn (\n\t\t\n\t\t\t{content}\n\t\t\n\t);\n}\n\nexport function NumberFormat({\n\tclassName,\n\tfallback,\n\tformatOptions,\n\tlocale,\n\tplaceholder,\n\tvalue,\n\t...props\n}: NumberFormatProps) {\n\tconst config = useFormatConfig();\n\treturn (\n\t\t\n\t);\n}\n\nexport function DecimalFormat({\n\tclassName,\n\tfallback,\n\tformatOptions,\n\tlocale,\n\tplaceholder,\n\tvalue,\n\t...props\n}: DecimalFormatProps) {\n\tconst config = useFormatConfig();\n\treturn (\n\t\t\n\t);\n}\n\nexport function CurrencyFormat({\n\tvalue,\n\tplaceholder,\n\tlocale,\n\tcurrency,\n\tfallback,\n\tformatOptions,\n\tclassName,\n\t...props\n}: CurrencyFormatProps) {\n\tconst config = useFormatConfig();\n\treturn (\n\t\t\n\t);\n}\n\nexport function PercentFormat({\n\tclassName,\n\tfallback,\n\tformatOptions,\n\tinput,\n\tlocale,\n\tplaceholder,\n\tvalue,\n\t...props\n}: PercentFormatProps) {\n\tconst config = useFormatConfig();\n\treturn (\n\t\t\n\t);\n}\n\nexport function BytesFormat({\n\tbase,\n\tbinary,\n\tclassName,\n\tfallback,\n\tmaximumFractionDigits,\n\tminimumFractionDigits,\n\tplaceholder,\n\tvalue,\n\t...props\n}: BytesFormatProps) {\n\treturn (\n\t\t\n\t);\n}\n\nexport function DurationFormat({\n\tclassName,\n\tdurationStyle,\n\tfallback,\n\tmaxParts,\n\tplaceholder,\n\tshowZero,\n\tunit,\n\tvalue,\n\t...props\n}: DurationFormatProps) {\n\treturn (\n\t\t\n\t);\n}\n\nexport function DateTimeFormat({\n\tclassName,\n\tfallback,\n\tformatOptions,\n\tlive = false,\n\tlocale,\n\tplaceholder,\n\trelative = false,\n\ttimeZone,\n\ttooltip = relative,\n\tvalue,\n\t...props\n}: DateTimeFormatProps) {\n\tconst config = useFormatConfig();\n\tconst now = useRelativeNow(value, live && relative);\n\tconst date = parseDateValue(value);\n\tif (!date) return <>{resolvePlaceholder(placeholder)};\n\tconst dateTimeOptions = formatOptions ?? { dateStyle: 'medium' as const, timeStyle: 'short' as const };\n\tconst absolute = formatDateTime(value, {\n\t\t...dateTimeOptions,\n\t\tfallback,\n\t\tlocale: locale ?? config.locale,\n\t\ttimeZone: timeZone ?? config.timeZone,\n\t});\n\tconst text = relative ? formatRelativeTime(value, { fallback, locale: locale ?? config.locale, now }) : absolute;\n\treturn (\n\t\t\n\t);\n}\n\nexport function DateFormat({ formatOptions, ...props }: DateFormatProps) {\n\treturn ;\n}\n\nexport function TimeFormat({ formatOptions, ...props }: TimeFormatProps) {\n\treturn ;\n}\n\nexport function RelativeTimeFormat({\n\tclassName,\n\tlive = true,\n\tlocale,\n\tnow,\n\tplaceholder,\n\ttitleOptions,\n\tvalue,\n\t...props\n}: RelativeTimeFormatProps) {\n\tconst config = useFormatConfig();\n\tconst currentNow = useRelativeNow(value, live, now);\n\tconst date = parseDateValue(value);\n\tif (!date) return <>{resolvePlaceholder(placeholder)};\n\tconst title = formatDateTime(value, {\n\t\tdateStyle: 'medium',\n\t\ttimeStyle: 'short',\n\t\t...titleOptions,\n\t\tlocale: locale ?? config.locale,\n\t\ttimeZone: config.timeZone,\n\t});\n\treturn (\n\t\t\n\t);\n}\n\nexport function BooleanFormat({\n\tbadge = true,\n\tclassName,\n\tfallback,\n\tfalseText,\n\tplaceholder,\n\ttrueText,\n\tvalue,\n\t...props\n}: BooleanFormatProps) {\n\tconst text = formatBoolean(value, { fallback, falseText, trueText });\n\tif (isFallbackText(text, fallback)) return <>{resolvePlaceholder(placeholder)};\n\treturn (\n\t\t\n\t\t\t{text}\n\t\t\n\t);\n}\n\nexport function PhoneNumberFormat({\n\tclassName,\n\tfallback,\n\tmask,\n\tplaceholder,\n\tseparator,\n\tvalue,\n\t...props\n}: PhoneNumberFormatProps) {\n\tconst text = formatPhoneNumber(value, { fallback, mask, separator });\n\tif (isFallbackText(text, fallback)) return <>{resolvePlaceholder(placeholder)};\n\treturn (\n\t\t\n\t\t\t{text}\n\t\t\n\t);\n}\n\nexport const AmountFormat = CurrencyFormat;\nexport const FormatAmount = CurrencyFormat;\nexport const FormatBoolean = BooleanFormat;\nexport const FormatBytes = BytesFormat;\nexport const FormatCurrency = CurrencyFormat;\nexport const FormatDate = DateFormat;\nexport const FormatDateTime = DateTimeFormat;\nexport const FormatDecimal = DecimalFormat;\nexport const FormatDuration = DurationFormat;\nexport const FormatNumber = NumberFormat;\nexport const FormatPercent = PercentFormat;\nexport const FormatPhoneNumber = PhoneNumberFormat;\nexport const FormatRelativeTime = RelativeTimeFormat;\nexport const FormatTime = TimeFormat;\n\nfunction ValueText({\n\tclassName,\n\tplaceholder,\n\tvalue,\n\t...props\n}: Omit, 'children'> & { placeholder?: ReactNode; value: string }) {\n\tif (isFallbackText(value)) return <>{resolvePlaceholder(placeholder)};\n\treturn (\n\t\t\n\t\t\t{value}\n\t\t\n\t);\n}\n\nfunction resolvePlaceholder(placeholder?: ReactNode) {\n\treturn placeholder ?? ;\n}\n\nfunction useRelativeNow(value: DateInput, enabled: boolean, fixedNow?: DateInput) {\n\tconst [now, setNow] = useState(() => fixedNow ?? Date.now());\n\tuseEffect(() => {\n\t\tif (!enabled || fixedNow !== undefined) return;\n\t\tlet handle: number | undefined;\n\t\tconst schedule = () => {\n\t\t\tconst interval = getRelativeTimeUpdateInterval(value);\n\t\t\tif (!interval) return;\n\t\t\thandle = window.setTimeout(() => {\n\t\t\t\tsetNow(Date.now());\n\t\t\t\tschedule();\n\t\t\t}, interval);\n\t\t};\n\t\tschedule();\n\t\treturn () => {\n\t\t\tif (handle) window.clearTimeout(handle);\n\t\t};\n\t}, [enabled, fixedNow, value]);\n\treturn fixedNow ?? now;\n}\n\nfunction isEmptyRenderable(value: ReactNode) {\n\treturn value === null || value === undefined || value === '';\n}\n\nfunction isFallbackText(value: string, fallback = '—') {\n\treturn value === fallback;\n}\n\nfunction joinClassNames(...classNames: Array) {\n\treturn classNames.filter(Boolean).join(' ');\n}\n", "type": "registry:component", "target": "@components/formats/format-components.tsx" }, { "path": "registry/default/ui/formats/index.ts", "content": "export {\n\tAmountFormat,\n\tBooleanFormat,\n\ttype BooleanFormatProps,\n\tBytesFormat,\n\ttype BytesFormatProps,\n\tCurrencyFormat,\n\ttype CurrencyFormatProps,\n\tDateFormat,\n\ttype DateFormatProps,\n\tDateTimeFormat,\n\ttype DateTimeFormatProps,\n\tDecimalFormat,\n\ttype DecimalFormatProps,\n\tDurationFormat,\n\ttype DurationFormatProps,\n\tEmptyPlaceholder,\n\ttype EmptyPlaceholderProps,\n\tFormatAmount,\n\tFormatBoolean,\n\tFormatBytes,\n\ttype FormatConfig,\n\tFormatCurrency,\n\tFormatDate,\n\tFormatDateTime,\n\tFormatDecimal,\n\tFormatDuration,\n\tFormatNumber,\n\tFormatPercent,\n\tFormatPhoneNumber,\n\tFormatProvider,\n\ttype FormatProviderProps,\n\tFormatRelativeTime,\n\tFormatTime,\n\tNumberFormat,\n\ttype NumberFormatProps,\n\tPercentFormat,\n\ttype PercentFormatProps,\n\tPhoneNumberFormat,\n\ttype PhoneNumberFormatProps,\n\tRelativeTimeFormat,\n\ttype RelativeTimeFormatProps,\n\tTimeFormat,\n\ttype TimeFormatProps,\n\tTruncateFormat,\n\ttype TruncateFormatProps,\n\tuseFormatConfig,\n\ttype ValueFormatProps,\n} from './format-components';\nexport {\n\ttype BooleanInput,\n\ttype DateInput,\n\ttype DurationInput,\n\ttype DurationStyle,\n\ttype DurationUnit,\n\ttype EmptyFormatValue,\n\ttype FormatBytesOptions,\n\ttype FormatCurrencyOptions,\n\ttype FormatDateTimeOptions,\n\ttype FormatFallbackOptions,\n\ttype FormatLocale,\n\ttype FormatNumberOptions,\n\ttype FormatPercentOptions,\n\ttype FormatPhoneNumberOptions,\n\ttype FormatRelativeTimeOptions,\n\tformatBoolean,\n\tformatBytes,\n\tformatCurrency,\n\tformatDate,\n\tformatDateTime,\n\tformatDecimal,\n\tformatDuration,\n\tformatNumber,\n\tformatPercent,\n\tformatPhoneNumber,\n\tformatRelativeTime,\n\tgetRelativeTimeUpdateInterval,\n\tisEmptyFormatValue,\n\ttype NumberInput,\n\tparseDateValue,\n\tparseDurationMilliseconds,\n\tparseFiniteNumber,\n} from './format-values';\n", "type": "registry:component", "target": "@components/formats/index.ts" } ], "type": "registry:component" }