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:
Amer Agovic
2026-04-18 10:43:52 -05:00
commit 94a9f32969
87 changed files with 19750 additions and 0 deletions
+259
View File
@@ -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);
}