Initial commit: bface library, build fixes, and refreshed docs
- Externalize all @tamagui/* and tamagui subpaths so dist no longer vendors Tamagui. - Emit TypeScript declarations with vite-plugin-dts; fix package exports types for ui/*. - Align initEnv with profiles: displayName, brandLogo, api.baseURL, themeColor, uiShell. - Stabilize tests with Node localStorage file; env tests pass. - Update README and component docs for services, menus, API client, and development.
This commit is contained in:
@@ -0,0 +1,259 @@
|
||||
export function prettyLabel(value) {
|
||||
if (!value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const withSpaces = String(value)
|
||||
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
||||
.replace(/[_-]+/g, ' ')
|
||||
.trim();
|
||||
|
||||
return withSpaces.charAt(0).toUpperCase() + withSpaces.slice(1);
|
||||
}
|
||||
|
||||
export function inferColumnType(value) {
|
||||
if (typeof value === 'boolean') {
|
||||
return 'boolean';
|
||||
}
|
||||
if (typeof value === 'number') {
|
||||
return 'number';
|
||||
}
|
||||
if (typeof value === 'string' && /^-?\d+(\.\d+)?$/.test(value)) {
|
||||
return 'number';
|
||||
}
|
||||
return 'text';
|
||||
}
|
||||
|
||||
export function normalizeColumnDefinition(field, columnDefinition = {}, sampleValue) {
|
||||
return {
|
||||
field,
|
||||
id: field,
|
||||
label: columnDefinition.label || columnDefinition.display_name || prettyLabel(field),
|
||||
sortable: columnDefinition.sortable ?? true,
|
||||
filterable: columnDefinition.filterable ?? true,
|
||||
align: columnDefinition.align || (inferColumnType(sampleValue) === 'number' ? 'right' : 'left'),
|
||||
width: columnDefinition.width ?? null,
|
||||
type: columnDefinition.type || inferColumnType(sampleValue),
|
||||
format: columnDefinition.format || null,
|
||||
renderer: columnDefinition.renderer || columnDefinition.render || null,
|
||||
currency: columnDefinition.currency || 'USD',
|
||||
priority: columnDefinition.priority || null,
|
||||
alwaysVisible: columnDefinition.alwaysVisible ?? false
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeColumnDefinitionsInput(input = {}) {
|
||||
if (Array.isArray(input)) {
|
||||
return Object.fromEntries(
|
||||
input
|
||||
.map((column) => {
|
||||
const field = column?.field || column?.id;
|
||||
if (!field) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
field,
|
||||
{
|
||||
...column,
|
||||
field,
|
||||
id: field,
|
||||
renderer: column.renderer || column.render || null
|
||||
}
|
||||
];
|
||||
})
|
||||
.filter(Boolean)
|
||||
);
|
||||
}
|
||||
|
||||
if (input && typeof input === 'object') {
|
||||
return Object.fromEntries(
|
||||
Object.entries(input).map(([field, column]) => [
|
||||
field,
|
||||
{
|
||||
...(column || {}),
|
||||
field: column?.field || column?.id || field,
|
||||
id: column?.id || column?.field || field,
|
||||
renderer: column?.renderer || column?.render || null
|
||||
}
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
export function normalizeColumnsArray(input = []) {
|
||||
if (Array.isArray(input)) {
|
||||
return input
|
||||
.map((column) => {
|
||||
const id = column?.id || column?.field;
|
||||
if (!id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...column,
|
||||
id,
|
||||
field: column.field || id,
|
||||
render: column.render || column.renderer || null
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
if (input && typeof input === 'object') {
|
||||
return Object.entries(input).map(([field, column]) => ({
|
||||
...(column || {}),
|
||||
id: column?.id || column?.field || field,
|
||||
field: column?.field || column?.id || field,
|
||||
render: column?.render || column?.renderer || null
|
||||
}));
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
export function compareValues(left, right, direction = 'asc') {
|
||||
if (left === right) {
|
||||
return 0;
|
||||
}
|
||||
if (left === null || left === undefined || left === '') {
|
||||
return 1;
|
||||
}
|
||||
if (right === null || right === undefined || right === '') {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const leftNumber = Number(left);
|
||||
const rightNumber = Number(right);
|
||||
const bothNumeric = !Number.isNaN(leftNumber) && !Number.isNaN(rightNumber);
|
||||
const result = bothNumeric
|
||||
? leftNumber - rightNumber
|
||||
: String(left).localeCompare(String(right), undefined, { sensitivity: 'base' });
|
||||
|
||||
return direction === 'desc' ? -result : result;
|
||||
}
|
||||
|
||||
export function getColumnKeysFromRows(rows = []) {
|
||||
const fields = new Set();
|
||||
for (const row of rows) {
|
||||
Object.keys(row || {}).forEach((field) => fields.add(field));
|
||||
}
|
||||
return Array.from(fields);
|
||||
}
|
||||
|
||||
export function resolveCellValue(row, column) {
|
||||
return row?.[column.field];
|
||||
}
|
||||
|
||||
export function resolveCellAlignment(column) {
|
||||
return column.align || 'left';
|
||||
}
|
||||
|
||||
export function resolveVisibleColumns(columns = [], viewportWidth = 0) {
|
||||
if (!columns.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let hiddenPriorities = new Set();
|
||||
if (viewportWidth > 0 && viewportWidth < 760) {
|
||||
hiddenPriorities = new Set(['wide', 'mid']);
|
||||
} else if (viewportWidth > 0 && viewportWidth < 980) {
|
||||
hiddenPriorities = new Set(['wide']);
|
||||
}
|
||||
|
||||
const filtered = columns.filter((column) => {
|
||||
if (column.alwaysVisible || !column.priority) {
|
||||
return true;
|
||||
}
|
||||
return !hiddenPriorities.has(column.priority);
|
||||
});
|
||||
|
||||
return filtered.length ? filtered : columns.slice(0, 1);
|
||||
}
|
||||
|
||||
export function areSortEntriesEqual(left = [], right = []) {
|
||||
if (left.length !== right.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return left.every(
|
||||
(entry, index) =>
|
||||
entry.field === right[index]?.field && entry.direction === right[index]?.direction
|
||||
);
|
||||
}
|
||||
|
||||
export function getColumnJustify(align = 'left') {
|
||||
if (align === 'right') {
|
||||
return 'flex-end';
|
||||
}
|
||||
if (align === 'center') {
|
||||
return 'center';
|
||||
}
|
||||
return 'flex-start';
|
||||
}
|
||||
|
||||
export function getColumnLayoutStyle(column = {}) {
|
||||
const width = column.width;
|
||||
|
||||
if (typeof width === 'number') {
|
||||
if (width > 0 && width <= 1) {
|
||||
return {
|
||||
flex: width,
|
||||
flexBasis: 0,
|
||||
minWidth: column.minWidth || 120
|
||||
};
|
||||
}
|
||||
if (width > 1) {
|
||||
return {
|
||||
flexShrink: 0,
|
||||
flexGrow: 0,
|
||||
width: `${width}em`,
|
||||
minWidth: `${width}em`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof width === 'string') {
|
||||
return {
|
||||
flexShrink: 0,
|
||||
flexGrow: 0,
|
||||
width,
|
||||
minWidth: width
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
flex: column.flex || 1,
|
||||
flexBasis: 0,
|
||||
minWidth: column.minWidth || 120
|
||||
};
|
||||
}
|
||||
|
||||
export function formatValueByColumn(value, column = {}) {
|
||||
if (value == null || value === '') {
|
||||
return '-';
|
||||
}
|
||||
|
||||
if (typeof column.format === 'function') {
|
||||
return column.format(value, column);
|
||||
}
|
||||
|
||||
if (column.type === 'currency') {
|
||||
const numeric = Number(value);
|
||||
if (!Number.isFinite(numeric)) {
|
||||
return '-';
|
||||
}
|
||||
return new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: column.currency || 'USD'
|
||||
}).format(numeric);
|
||||
}
|
||||
|
||||
if (column.type === 'boolean') {
|
||||
return value ? 'Yes' : 'No';
|
||||
}
|
||||
|
||||
return String(value);
|
||||
}
|
||||
Reference in New Issue
Block a user