wip
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -70,6 +70,18 @@ function parseVoltageSortKey(voltageLabel: string): number {
|
||||
function compactNumericForLocale(value: string, locale: 'en' | 'de'): string {
|
||||
const v = normalizeValue(value);
|
||||
if (!v) return '';
|
||||
|
||||
// Compact common bending-radius style: "15xD (Single core); 12xD (Multi core)" -> "15/12xD".
|
||||
// Keep semantics, reduce width. Never truncate with ellipses.
|
||||
if (/\d+xD/i.test(v)) {
|
||||
const nums = Array.from(v.matchAll(/(\d+)xD/gi)).map(m => m[1]).filter(Boolean);
|
||||
const unique: string[] = [];
|
||||
for (const n of nums) {
|
||||
if (!unique.includes(n)) unique.push(n);
|
||||
}
|
||||
if (unique.length) return `${unique.join('/') }xD`;
|
||||
}
|
||||
|
||||
const hasDigit = /\d/.test(v);
|
||||
if (!hasDigit) return v;
|
||||
const trimmed = v.replace(/\s+/g, ' ').trim();
|
||||
@@ -242,6 +254,12 @@ function denseAbbrevLabel(args: { key: string; locale: 'en' | 'de'; unit?: strin
|
||||
return args.locale === 'de' ? 'Leiter' : 'Cond.';
|
||||
case 'shape':
|
||||
return args.locale === 'de' ? 'Form' : 'Shape';
|
||||
// Electrical
|
||||
case 'cap':
|
||||
// Capacitance. Use a clear label; lowercase "cap" looks like an internal key.
|
||||
return `Cap${suffix}`;
|
||||
case 'X':
|
||||
return `X${suffix}`;
|
||||
case 'test_volt':
|
||||
return `U_test${suffix}`;
|
||||
case 'rated_volt':
|
||||
@@ -265,13 +283,14 @@ function denseAbbrevLabel(args: { key: string; locale: 'en' | 'de'; unit?: strin
|
||||
}
|
||||
}
|
||||
|
||||
function summarizeOptions(options: string[] | undefined, maxItems: number = 3): string {
|
||||
function summarizeOptions(options: string[] | undefined): string {
|
||||
const vals = (options || []).map(normalizeValue).filter(Boolean);
|
||||
if (vals.length === 0) return '';
|
||||
const uniq = Array.from(new Set(vals));
|
||||
if (uniq.length === 1) return uniq[0];
|
||||
if (uniq.length <= maxItems) return uniq.join(' / ');
|
||||
return `${uniq.slice(0, maxItems).join(' / ')} / ...`;
|
||||
// Never use ellipsis truncation in datasheets. Prefer full value list.
|
||||
// (Long values should be handled by layout; if needed we can later add wrapping rules.)
|
||||
return uniq.join(' / ');
|
||||
}
|
||||
|
||||
function parseNumericOption(value: string): number | null {
|
||||
@@ -294,10 +313,17 @@ function summarizeNumericRange(options: string[] | undefined): { ok: boolean; te
|
||||
return { ok: true, text: `${fmt(min)}–${fmt(max)}` };
|
||||
}
|
||||
|
||||
function summarizeSmartOptions(label: string, options: string[] | undefined): string {
|
||||
function summarizeSmartOptions(_label: string, options: string[] | undefined): string {
|
||||
const range = summarizeNumericRange(options);
|
||||
if (range.ok) return range.text;
|
||||
return summarizeOptions(options, 3);
|
||||
return summarizeOptions(options);
|
||||
}
|
||||
|
||||
function normalizeDesignation(value: string): string {
|
||||
return String(value || '')
|
||||
.toUpperCase()
|
||||
.replace(/-\d+$/g, '')
|
||||
.replace(/[^A-Z0-9]+/g, '');
|
||||
}
|
||||
|
||||
function buildExcelModel(args: { product: ProductData; locale: 'en' | 'de' }): BuildExcelModelResult {
|
||||
@@ -361,6 +387,14 @@ function buildExcelModel(args: { product: ProductData; locale: 'en' | 'de' }): B
|
||||
'cpr class': { header: 'CPR class', unit: '', key: 'cpr' },
|
||||
'flame retardant': { header: 'Flame retardant', unit: '', key: 'flame' },
|
||||
'self-extinguishing of single cable': { header: 'Flame retardant', unit: '', key: 'flame' },
|
||||
|
||||
// High-value electrical/screen columns
|
||||
'capacitance (approx.)': { header: 'Capacitance', unit: 'uF/km', key: 'cap' },
|
||||
'capacitance': { header: 'Capacitance', unit: 'uF/km', key: 'cap' },
|
||||
'reactance': { header: 'Reactance', unit: 'Ohm/km', key: 'X' },
|
||||
'diameter over screen': { header: 'Diameter over screen', unit: 'mm', key: 'D_screen' },
|
||||
'metallic screen mm2': { header: 'Metallic screen', unit: 'mm2', key: 'S_screen' },
|
||||
'metallic screen': { header: 'Metallic screen', unit: 'mm2', key: 'S_screen' },
|
||||
};
|
||||
|
||||
const excelKeys = Object.keys(sample).filter(k => k && k !== 'Part Number' && k !== 'Units');
|
||||
@@ -464,6 +498,10 @@ function buildExcelModel(args: { product: ProductData; locale: 'en' | 'de' }): B
|
||||
const denseTableKeyOrder = [
|
||||
'Cond',
|
||||
'shape',
|
||||
// Electrical properties (when present)
|
||||
'cap',
|
||||
'X',
|
||||
// Dimensions and ratings
|
||||
'DI',
|
||||
'RI',
|
||||
'Wi',
|
||||
@@ -473,6 +511,9 @@ function buildExcelModel(args: { product: ProductData; locale: 'en' | 'de' }): B
|
||||
'Wm',
|
||||
'Rbv',
|
||||
'Ø',
|
||||
// Screen data (when present)
|
||||
'D_screen',
|
||||
'S_screen',
|
||||
'Fzv',
|
||||
'G',
|
||||
] as const;
|
||||
@@ -510,16 +551,33 @@ function buildExcelModel(args: { product: ProductData; locale: 'en' | 'de' }): B
|
||||
if (!mappedByKey.has(c.mapping.key)) mappedByKey.set(c.mapping.key, c);
|
||||
}
|
||||
|
||||
// If conductor material is missing in Excel, derive it from designation.
|
||||
// NA... => Al, N... => Cu (common for this dataset).
|
||||
if (!mappedByKey.has('Cond')) {
|
||||
mappedByKey.set('Cond', {
|
||||
excelKey: '',
|
||||
mapping: { header: 'Cond.', unit: '', key: 'Cond' },
|
||||
});
|
||||
}
|
||||
|
||||
const orderedTableColumns = denseTableKeyOrder
|
||||
.filter(k => mappedByKey.has(k))
|
||||
.map(k => mappedByKey.get(k)!)
|
||||
.map(({ excelKey, mapping }) => {
|
||||
const unit = normalizeUnit(units[excelKey] || mapping.unit || '');
|
||||
const unit = normalizeUnit((excelKey ? units[excelKey] : '') || mapping.unit || '');
|
||||
return {
|
||||
key: mapping.key,
|
||||
label: denseAbbrevLabel({ key: mapping.key, locale: args.locale, unit }) || formatExcelHeaderLabel(excelKey, unit),
|
||||
get: (rowIndex: number) => {
|
||||
const srcRowIndex = indices[rowIndex];
|
||||
|
||||
if (mapping.key === 'Cond' && !excelKey) {
|
||||
const pn = normalizeDesignation(args.product.name || args.product.slug || args.product.sku || '');
|
||||
if (/^NA/.test(pn)) return 'Al';
|
||||
if (/^N/.test(pn)) return 'Cu';
|
||||
return '';
|
||||
}
|
||||
|
||||
const raw = excelKey ? normalizeValue(String(compatibleRows[srcRowIndex]?.[excelKey] ?? '')) : '';
|
||||
return compactCellForDenseTable(raw, unit, args.locale);
|
||||
},
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as React from 'react';
|
||||
import { Document, Image, Page, Text, View } from '@react-pdf/renderer';
|
||||
|
||||
import type { DatasheetModel, DatasheetVoltageTable } from '../model/types';
|
||||
import { CONFIG } from '../model/utils';
|
||||
import { styles } from './styles';
|
||||
import { Header } from './components/Header';
|
||||
import { Footer } from './components/Footer';
|
||||
@@ -25,9 +26,9 @@ function chunk<T>(arr: T[], size: number): T[][] {
|
||||
export function DatasheetDocument(props: { model: DatasheetModel; assets: Assets }): React.ReactElement {
|
||||
const { model, assets } = props;
|
||||
const headerTitle = model.labels.datasheet;
|
||||
const footerLeft = `${model.labels.sku}: ${model.product.sku}`;
|
||||
|
||||
const firstColLabel = model.locale === 'de' ? 'Adern & Querschnitt' : 'Cores & cross-section';
|
||||
// Dense tables require compact headers (no wrapping). Use standard abbreviations.
|
||||
const firstColLabel = model.locale === 'de' ? 'Adern & QS' : 'Cores & CS';
|
||||
|
||||
const tablePages: Array<{ table: DatasheetVoltageTable; rows: DatasheetVoltageTable['rows'] }> =
|
||||
model.voltageTables.flatMap(t => {
|
||||
@@ -40,7 +41,7 @@ export function DatasheetDocument(props: { model: DatasheetModel; assets: Assets
|
||||
<Document>
|
||||
<Page size="A4" style={styles.page}>
|
||||
<Header title={headerTitle} logoDataUrl={assets.logoDataUrl} qrDataUrl={assets.qrDataUrl} />
|
||||
<Footer leftText={footerLeft} locale={model.locale} />
|
||||
<Footer locale={model.locale} siteUrl={CONFIG.siteUrl} />
|
||||
|
||||
<Text style={styles.h1}>{model.product.name}</Text>
|
||||
{model.product.categoriesLine ? <Text style={styles.subhead}>{model.product.categoriesLine}</Text> : null}
|
||||
@@ -66,19 +67,18 @@ export function DatasheetDocument(props: { model: DatasheetModel; assets: Assets
|
||||
) : null}
|
||||
</Page>
|
||||
|
||||
{tablePages.map((p, index) => (
|
||||
<Page key={`${p.table.voltageLabel}-${index}`} size="A4" style={styles.page}>
|
||||
<Header title={headerTitle} logoDataUrl={assets.logoDataUrl} qrDataUrl={assets.qrDataUrl} />
|
||||
<Footer leftText={footerLeft} locale={model.locale} />
|
||||
{tablePages.map((p, index) => (
|
||||
<Page key={`${p.table.voltageLabel}-${index}`} size="A4" style={styles.page}>
|
||||
<Header title={headerTitle} logoDataUrl={assets.logoDataUrl} qrDataUrl={assets.qrDataUrl} />
|
||||
<Footer locale={model.locale} siteUrl={CONFIG.siteUrl} />
|
||||
|
||||
<Section title={`${model.labels.crossSection} — ${p.table.voltageLabel}`}>
|
||||
{p.table.metaItems.length ? <KeyValueGrid items={p.table.metaItems} /> : null}
|
||||
</Section>
|
||||
|
||||
<DenseTable table={{ columns: p.table.columns, rows: p.rows }} firstColLabel={firstColLabel} />
|
||||
</Page>
|
||||
))}
|
||||
</Document>
|
||||
);
|
||||
</Page>
|
||||
))}
|
||||
</Document>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,38 +15,99 @@ export function DenseTable(props: {
|
||||
const cols = props.table.columns;
|
||||
const rows = props.table.rows;
|
||||
|
||||
const cfgPct = cols.length >= 12 ? 0.28 : 0.32;
|
||||
const dataPct = 1 - cfgPct;
|
||||
const each = cols.length ? dataPct / cols.length : dataPct;
|
||||
const cfgW = `${Math.round(cfgPct * 100)}%`;
|
||||
const dataW = `${Math.round(clamp(each, 0.03, 0.12) * 1000) / 10}%`;
|
||||
const noWrapHeader = (label: string): string => {
|
||||
const raw = String(label || '').trim();
|
||||
if (!raw) return '';
|
||||
|
||||
// Ensure the header never wraps into a second line.
|
||||
// - Remove whitespace break opportunities (NBSP)
|
||||
// NOTE: Avoid inserting zero-width joiners between letters.
|
||||
// Some PDF viewers render them with spacing/odd glyph behavior.
|
||||
// This is intentionally aggressive because broken headers destroy scanability.
|
||||
return raw.replace(/\s+/g, '\u00A0');
|
||||
};
|
||||
|
||||
// Column widths: use explicit percentages (no rounding gaps) so the table always
|
||||
// consumes the full content width.
|
||||
// Goal:
|
||||
// - keep the designation column *not too wide*
|
||||
// - guarantee enough width for data headers when there is available space
|
||||
const cfgMin = 0.18;
|
||||
const cfgMax = 0.30;
|
||||
|
||||
let cfgPct = cols.length >= 14 ? 0.22 : cols.length >= 12 ? 0.24 : cols.length >= 10 ? 0.26 : 0.30;
|
||||
cfgPct = clamp(cfgPct, cfgMin, cfgMax);
|
||||
|
||||
const minDataPct = cols.length >= 14 ? 0.045 : cols.length >= 12 ? 0.05 : cols.length >= 10 ? 0.055 : 0.06;
|
||||
// If the initial cfgPct leaves too little width per data column, reduce cfgPct.
|
||||
const cfgPctMaxForMinData = 1 - cols.length * minDataPct;
|
||||
if (Number.isFinite(cfgPctMaxForMinData)) {
|
||||
cfgPct = Math.min(cfgPct, cfgPctMaxForMinData);
|
||||
cfgPct = clamp(cfgPct, cfgMin, cfgMax);
|
||||
}
|
||||
|
||||
const cfgW = `${(cfgPct * 100).toFixed(4)}%`;
|
||||
const dataTotal = 1 - cfgPct;
|
||||
const each = cols.length ? dataTotal / cols.length : dataTotal;
|
||||
const dataWs = cols.map((_, idx) => {
|
||||
// Keep the last column as the remainder so percentages sum to exactly 100%.
|
||||
if (idx === cols.length - 1) {
|
||||
const used = each * Math.max(0, cols.length - 1);
|
||||
const remainder = Math.max(0, dataTotal - used);
|
||||
return `${(remainder * 100).toFixed(4)}%`;
|
||||
}
|
||||
return `${(each * 100).toFixed(4)}%`;
|
||||
});
|
||||
|
||||
return (
|
||||
<View style={styles.tableWrap}>
|
||||
<View style={styles.tableHeader}>
|
||||
<View style={styles.tableHeader} wrap={false}>
|
||||
<View style={{ width: cfgW }}>
|
||||
<Text style={styles.tableHeaderCell}>{props.firstColLabel}</Text>
|
||||
<Text
|
||||
style={[
|
||||
styles.tableHeaderCell,
|
||||
styles.tableHeaderCellCfg,
|
||||
cols.length ? styles.tableHeaderCellDivider : null,
|
||||
]}
|
||||
wrap={false}
|
||||
>
|
||||
{noWrapHeader(props.firstColLabel)}
|
||||
</Text>
|
||||
</View>
|
||||
{cols.map(c => (
|
||||
<View key={c.key} style={{ width: dataW }}>
|
||||
<Text style={styles.tableHeaderCell}>{c.label}</Text>
|
||||
</View>
|
||||
))}
|
||||
{cols.map((c, idx) => {
|
||||
const isLast = idx === cols.length - 1;
|
||||
return (
|
||||
<View key={c.key} style={{ width: dataWs[idx] }}>
|
||||
<Text
|
||||
style={[styles.tableHeaderCell, !isLast ? styles.tableHeaderCellDivider : null]}
|
||||
wrap={false}
|
||||
>
|
||||
{noWrapHeader(c.label)}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
|
||||
{rows.map((r, ri) => (
|
||||
<View key={`${r.configuration}-${ri}`} style={[styles.tableRow, ri % 2 === 0 ? styles.tableRowAlt : null]}>
|
||||
<View
|
||||
key={`${r.configuration}-${ri}`}
|
||||
style={[styles.tableRow, ri % 2 === 0 ? styles.tableRowAlt : null]}
|
||||
wrap={false}
|
||||
>
|
||||
<View style={{ width: cfgW }}>
|
||||
<Text style={styles.tableCell}>{r.configuration}</Text>
|
||||
<Text style={[styles.tableCell, styles.tableCellCfg, cols.length ? styles.tableCellDivider : null]}>{r.configuration}</Text>
|
||||
</View>
|
||||
{r.cells.map((cell, ci) => (
|
||||
<View key={`${cols[ci]?.key || ci}`} style={{ width: dataW }}>
|
||||
<Text style={styles.tableCell}>{cell}</Text>
|
||||
</View>
|
||||
))}
|
||||
{r.cells.map((cell, ci) => {
|
||||
const isLast = ci === r.cells.length - 1;
|
||||
return (
|
||||
<View key={`${cols[ci]?.key || ci}`} style={{ width: dataWs[ci] }}>
|
||||
<Text style={[styles.tableCell, !isLast ? styles.tableCellDivider : null]}>{cell}</Text>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,19 +3,20 @@ import { Text, View } from '@react-pdf/renderer';
|
||||
|
||||
import { styles } from '../styles';
|
||||
|
||||
export function Footer(props: { leftText: string; locale: 'en' | 'de' }): React.ReactElement {
|
||||
export function Footer(props: { locale: 'en' | 'de'; siteUrl?: string }): React.ReactElement {
|
||||
const date = new Date().toLocaleDateString(props.locale === 'en' ? 'en-US' : 'de-DE', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
});
|
||||
|
||||
const siteUrl = props.siteUrl || 'https://klz-cables.com';
|
||||
|
||||
return (
|
||||
<View style={styles.footer} fixed>
|
||||
<Text>{props.leftText}</Text>
|
||||
<Text>{siteUrl}</Text>
|
||||
<Text>{date}</Text>
|
||||
<Text render={({ pageNumber, totalPages }) => `${pageNumber}/${totalPages}`} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,18 +8,36 @@ export function KeyValueGrid(props: { items: KeyValueItem[] }): React.ReactEleme
|
||||
const items = (props.items || []).filter(i => i.label && i.value);
|
||||
if (!items.length) return null;
|
||||
|
||||
// 4-column layout: (label, value, label, value)
|
||||
const rows: Array<[KeyValueItem, KeyValueItem | null]> = [];
|
||||
for (let i = 0; i < items.length; i += 2) {
|
||||
rows.push([items[i], items[i + 1] || null]);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.kvGrid}>
|
||||
{items.map((item, index) => {
|
||||
const isLast = index === items.length - 1;
|
||||
const valueText = item.unit ? `${item.value} ${item.unit}` : item.value;
|
||||
{rows.map(([left, right], rowIndex) => {
|
||||
const isLast = rowIndex === rows.length - 1;
|
||||
const leftValue = left.unit ? `${left.value} ${left.unit}` : left.value;
|
||||
const rightValue = right ? (right.unit ? `${right.value} ${right.unit}` : right.value) : '';
|
||||
|
||||
return (
|
||||
<View key={`${item.label}-${index}`} style={[styles.kvRow, isLast ? styles.kvRowLast : null]}>
|
||||
<View style={styles.kvLabel}>
|
||||
<Text style={styles.kvLabelText}>{item.label}</Text>
|
||||
<View
|
||||
key={`${left.label}-${rowIndex}`}
|
||||
style={[styles.kvRow, isLast ? styles.kvRowLast : null]}
|
||||
wrap={false}
|
||||
>
|
||||
<View style={[styles.kvCell, { width: '23%' }]}>
|
||||
<Text style={styles.kvLabelText}>{left.label}</Text>
|
||||
</View>
|
||||
<View style={styles.kvValue}>
|
||||
<Text style={styles.kvValueText}>{valueText}</Text>
|
||||
<View style={[styles.kvCell, styles.kvMidDivider, { width: '27%' }]}>
|
||||
<Text style={styles.kvValueText}>{leftValue}</Text>
|
||||
</View>
|
||||
<View style={[styles.kvCell, { width: '23%' }]}>
|
||||
<Text style={styles.kvLabelText}>{right?.label || ''}</Text>
|
||||
</View>
|
||||
<View style={[styles.kvCell, { width: '27%' }]}>
|
||||
<Text style={styles.kvValueText}>{rightValue}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
@@ -27,4 +45,3 @@ export function KeyValueGrid(props: { items: KeyValueItem[] }): React.ReactEleme
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,12 +3,16 @@ import { Text, View } from '@react-pdf/renderer';
|
||||
|
||||
import { styles } from '../styles';
|
||||
|
||||
export function Section(props: { title: string; children: React.ReactNode }): React.ReactElement {
|
||||
export function Section(props: {
|
||||
title: string;
|
||||
children: React.ReactNode;
|
||||
boxed?: boolean;
|
||||
}): React.ReactElement {
|
||||
const boxed = props.boxed ?? true;
|
||||
return (
|
||||
<View style={styles.section}>
|
||||
<View style={boxed ? styles.section : styles.sectionPlain}>
|
||||
<Text style={styles.sectionTitle}>{props.title}</Text>
|
||||
{props.children}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { StyleSheet } from '@react-pdf/renderer';
|
||||
import { Font, StyleSheet } from '@react-pdf/renderer';
|
||||
|
||||
// Prevent automatic word hyphenation, which can create multi-line table headers
|
||||
// even when we try to keep them single-line.
|
||||
Font.registerHyphenationCallback(word => [word]);
|
||||
|
||||
export const COLORS = {
|
||||
navy: '#0E2A47',
|
||||
@@ -76,6 +80,10 @@ export const styles = StyleSheet.create({
|
||||
padding: 14,
|
||||
marginBottom: 14,
|
||||
},
|
||||
sectionPlain: {
|
||||
paddingVertical: 2,
|
||||
marginBottom: 12,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 10,
|
||||
fontWeight: 700,
|
||||
@@ -86,34 +94,56 @@ export const styles = StyleSheet.create({
|
||||
body: { fontSize: 10, lineHeight: 1.5, color: COLORS.darkGray },
|
||||
|
||||
kvGrid: {
|
||||
width: '100%',
|
||||
borderWidth: 1,
|
||||
borderColor: COLORS.lightGray,
|
||||
},
|
||||
kvRow: { flexDirection: 'row', borderBottomWidth: 1, borderBottomColor: COLORS.lightGray },
|
||||
kvRow: {
|
||||
flexDirection: 'row',
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: COLORS.lightGray,
|
||||
},
|
||||
kvRowLast: { borderBottomWidth: 0 },
|
||||
kvLabel: {
|
||||
width: '45%',
|
||||
paddingVertical: 6,
|
||||
paddingHorizontal: 8,
|
||||
backgroundColor: COLORS.almostWhite,
|
||||
kvCell: { paddingVertical: 6, paddingHorizontal: 8 },
|
||||
// Visual separator between (label,value) pairs in the 4-col KV grid.
|
||||
// Matches the engineering-table look and improves scanability.
|
||||
kvMidDivider: {
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: COLORS.lightGray,
|
||||
},
|
||||
kvValue: { width: '55%', paddingVertical: 6, paddingHorizontal: 8 },
|
||||
kvLabelText: { fontSize: 8.5, fontWeight: 700, color: COLORS.mediumGray },
|
||||
kvValueText: { fontSize: 9.5, color: COLORS.darkGray },
|
||||
|
||||
tableWrap: { borderWidth: 1, borderColor: COLORS.lightGray },
|
||||
tableHeader: { flexDirection: 'row', backgroundColor: '#6B707A' },
|
||||
tableWrap: { width: '100%', borderWidth: 1, borderColor: COLORS.lightGray },
|
||||
tableHeader: {
|
||||
width: '100%',
|
||||
flexDirection: 'row',
|
||||
backgroundColor: '#FFFFFF',
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: COLORS.lightGray,
|
||||
},
|
||||
tableHeaderCell: {
|
||||
paddingVertical: 5,
|
||||
paddingHorizontal: 4,
|
||||
fontSize: 6.6,
|
||||
fontWeight: 700,
|
||||
color: '#FFFFFF',
|
||||
color: COLORS.navy,
|
||||
},
|
||||
tableRow: { flexDirection: 'row', borderBottomWidth: 1, borderBottomColor: COLORS.lightGray },
|
||||
tableHeaderCellCfg: {
|
||||
paddingHorizontal: 6,
|
||||
},
|
||||
tableHeaderCellDivider: {
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: COLORS.lightGray,
|
||||
},
|
||||
tableRow: { width: '100%', flexDirection: 'row', borderBottomWidth: 1, borderBottomColor: COLORS.lightGray },
|
||||
tableRowAlt: { backgroundColor: COLORS.almostWhite },
|
||||
tableCell: { paddingVertical: 4, paddingHorizontal: 4, fontSize: 6.6, color: COLORS.darkGray },
|
||||
tableCellCfg: {
|
||||
paddingHorizontal: 6,
|
||||
},
|
||||
tableCellDivider: {
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: COLORS.lightGray,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user