Expand theme system and refresh UI components
This commit is contained in:
@@ -2,6 +2,7 @@ import React, { useEffect, useRef } from 'react';
|
||||
import { Button, Checkbox, ScrollView, Separator, Text, XStack, YStack } from 'tamagui';
|
||||
import { getIcon } from '../IconMapper.jsx';
|
||||
import { useGridView } from './context.js';
|
||||
import { getTypographyRoleProps } from '../../styles/index.js';
|
||||
import {
|
||||
formatValueByColumn,
|
||||
getColumnJustify,
|
||||
@@ -12,7 +13,7 @@ import {
|
||||
|
||||
function DefaultGridCellRenderer({ value, column }) {
|
||||
return (
|
||||
<Text width="100%" textAlign={column.align || 'left'} opacity={value == null || value === '' ? 0.6 : 1}>
|
||||
<Text width="100%" textAlign={column.align || 'left'} color="$textPrimary" opacity={value == null || value === '' ? 0.6 : 1}>
|
||||
{formatValueByColumn(value, column)}
|
||||
</Text>
|
||||
);
|
||||
@@ -29,15 +30,26 @@ function UtilityCell({ rowId }) {
|
||||
if (grid.nested) {
|
||||
return (
|
||||
<XStack width={36} alignItems="center" justifyContent="center">
|
||||
{ChevronRightIcon ? <ChevronRightIcon size={16} /> : <Text>{'>'}</Text>}
|
||||
{ChevronRightIcon ? <ChevronRightIcon size="sm" color="$textSecondary" /> : <Text color="$textSecondary">{'>'}</Text>}
|
||||
</XStack>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<XStack width={36} alignItems="center" justifyContent="center">
|
||||
<Checkbox checked={grid.selectedIds.has(rowId)} onCheckedChange={() => grid.toggleSelectRow(rowId)}>
|
||||
<Checkbox.Indicator />
|
||||
<Checkbox
|
||||
checked={grid.selectedIds.has(rowId)}
|
||||
onCheckedChange={() => grid.toggleSelectRow(rowId)}
|
||||
borderColor="$lineStrong"
|
||||
backgroundColor="$bgPanel"
|
||||
focusStyle={{ borderColor: '$accent' }}
|
||||
>
|
||||
<Checkbox.Indicator>
|
||||
{(() => {
|
||||
const Check = getIcon('check');
|
||||
return Check ? <Check size="sm" color="$accent" /> : null;
|
||||
})()}
|
||||
</Checkbox.Indicator>
|
||||
</Checkbox>
|
||||
</XStack>
|
||||
);
|
||||
@@ -80,21 +92,46 @@ export function TableHeader({ visible = true, showTopBorder = true }) {
|
||||
}
|
||||
|
||||
const activeColumns = grid.visibleColumns?.length ? grid.visibleColumns : grid.resolvedColumns;
|
||||
const CaretUp = getIcon('caret-up');
|
||||
const CaretDown = getIcon('caret-down');
|
||||
const allIds = (grid.rows || []).map((row) => row?.id).filter((id) => id != null);
|
||||
const selectedCount = allIds.filter((id) => grid.selectedIds.has(id)).length;
|
||||
const allSelected = allIds.length > 0 && selectedCount === allIds.length;
|
||||
const someSelected = selectedCount > 0 && !allSelected;
|
||||
|
||||
return (
|
||||
<XStack
|
||||
alignItems="stretch"
|
||||
borderTopWidth={showTopBorder ? 1 : 0}
|
||||
borderBottomWidth={1}
|
||||
borderColor="$accentBorder"
|
||||
backgroundColor="$accentSurface"
|
||||
borderColor="$lineSubtle"
|
||||
backgroundColor="transparent"
|
||||
paddingHorizontal="$2"
|
||||
>
|
||||
{grid.selectable || grid.nested ? <XStack width={36} /> : null}
|
||||
{grid.selectable || grid.nested ? (
|
||||
<XStack width={36} alignItems="center" justifyContent="center">
|
||||
{grid.selectable ? (
|
||||
<Checkbox
|
||||
checked={allSelected ? true : someSelected ? 'indeterminate' : false}
|
||||
onCheckedChange={() => grid.toggleSelectAll?.()}
|
||||
borderColor="$lineStrong"
|
||||
backgroundColor="$bgPanel"
|
||||
focusStyle={{ borderColor: '$accent' }}
|
||||
>
|
||||
<Checkbox.Indicator>
|
||||
{(() => {
|
||||
const Check = getIcon('check');
|
||||
return Check ? <Check size="sm" color="$accent" /> : null;
|
||||
})()}
|
||||
</Checkbox.Indicator>
|
||||
</Checkbox>
|
||||
) : null}
|
||||
</XStack>
|
||||
) : null}
|
||||
{activeColumns.map((column) => {
|
||||
const activeSort = grid.sortBy.find((entry) => entry.field === column.field);
|
||||
const sortLabel =
|
||||
activeSort?.direction === 'asc' ? '↑' : activeSort?.direction === 'desc' ? '↓' : '';
|
||||
const isActive = Boolean(activeSort);
|
||||
const direction = activeSort?.direction;
|
||||
|
||||
return (
|
||||
<Button
|
||||
@@ -107,10 +144,28 @@ export function TableHeader({ visible = true, showTopBorder = true }) {
|
||||
paddingVertical="$3"
|
||||
paddingHorizontal="$2"
|
||||
{...getColumnLayoutStyle(column)}
|
||||
hoverStyle={column.sortable ? { backgroundColor: '$bgPage' } : undefined}
|
||||
pressStyle={column.sortable ? { backgroundColor: '$bgPanelElev' } : undefined}
|
||||
>
|
||||
<Text width="100%" textAlign={column.align || 'left'} fontWeight="700">
|
||||
{column.label}{sortLabel ? ` ${sortLabel}` : ''}
|
||||
</Text>
|
||||
<XStack width="100%" alignItems="center" justifyContent={getColumnJustify(column.align)} gap="$2">
|
||||
<Text
|
||||
width="auto"
|
||||
textAlign={column.align || 'left'}
|
||||
numberOfLines={1}
|
||||
{...getTypographyRoleProps('tableHeader')}
|
||||
>
|
||||
{column.label}
|
||||
</Text>
|
||||
{column.sortable ? (
|
||||
isActive ? (
|
||||
direction === 'asc'
|
||||
? (CaretUp ? <CaretUp size="xs" color="$textSecondary" /> : null)
|
||||
: (CaretDown ? <CaretDown size="xs" color="$textSecondary" /> : null)
|
||||
) : (
|
||||
CaretDown ? <CaretDown size="xs" color="$textMuted" style={{ opacity: 0.6 }} /> : null
|
||||
)
|
||||
) : null}
|
||||
</XStack>
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
@@ -131,7 +186,7 @@ export function TableBodyView({ visible = true }) {
|
||||
if (grid.error) {
|
||||
return (
|
||||
<YStack flex={1} alignItems="center" justifyContent="center" padding="$5">
|
||||
<Text color="#b91c1c">{grid.error}</Text>
|
||||
<Text color="$danger" fontWeight="600">{grid.error}</Text>
|
||||
</YStack>
|
||||
);
|
||||
}
|
||||
@@ -144,7 +199,8 @@ export function TableBodyView({ visible = true }) {
|
||||
<XStack
|
||||
alignItems="stretch"
|
||||
paddingHorizontal="$2"
|
||||
backgroundColor={grid.selectedIds.has(row.id) ? '$accentSurface' : '$background'}
|
||||
backgroundColor={grid.selectedIds.has(row.id) ? '$accentBg' : index % 2 === 1 ? '$bgPage' : 'transparent'}
|
||||
hoverStyle={{ backgroundColor: '$bgPage' }}
|
||||
>
|
||||
{grid.selectable || grid.nested ? <UtilityCell rowId={row.id} /> : null}
|
||||
{activeColumns.map((column) => {
|
||||
@@ -166,19 +222,22 @@ export function TableBodyView({ visible = true }) {
|
||||
);
|
||||
})}
|
||||
</XStack>
|
||||
<Separator />
|
||||
<Separator borderColor="$lineSubtle" />
|
||||
</YStack>
|
||||
))}
|
||||
|
||||
{!grid.rows.length && !grid.isLoading ? (
|
||||
<YStack minHeight={120} alignItems="center" justifyContent="center" padding="$5">
|
||||
<Text color="$color" opacity={0.7}>No records available.</Text>
|
||||
<Text color="$textSecondary" fontWeight="600">No records available</Text>
|
||||
<Text color="$textMuted" fontSize="$3">There's nothing to show here yet.</Text>
|
||||
</YStack>
|
||||
) : null}
|
||||
|
||||
{grid.isLoading ? (
|
||||
<YStack minHeight={64} alignItems="center" justifyContent="center" padding="$4">
|
||||
<Text color="$color" opacity={0.7}>Loading...</Text>
|
||||
{grid.isLoading && !grid.rows.length ? (
|
||||
<YStack minHeight={64} padding="$4" gap="$2">
|
||||
{[0, 1, 2].map((i) => (
|
||||
<XStack key={i} height={46} borderRadius="$radiusMd" backgroundColor="$bgPage" />
|
||||
))}
|
||||
</YStack>
|
||||
) : null}
|
||||
</YStack>
|
||||
@@ -205,21 +264,21 @@ export function TableFooter({ visible = true }) {
|
||||
padding="$3"
|
||||
minHeight={56}
|
||||
borderTopWidth={1}
|
||||
borderTopColor="$borderColor"
|
||||
backgroundColor="$background"
|
||||
borderTopColor="$lineSubtle"
|
||||
backgroundColor="$bgPanel"
|
||||
flexWrap="wrap"
|
||||
>
|
||||
<Text color="$color" opacity={0.7}>
|
||||
<Text color="$textMuted">
|
||||
{grid.total} records
|
||||
</Text>
|
||||
|
||||
<XStack gap="$1" alignItems="center" flexWrap="wrap">
|
||||
<XStack gap="$1" alignItems="center" flexWrap="wrap" padding="$1" borderWidth={1} borderColor="$lineSubtle" borderRadius="$radiusMd" backgroundColor="$bgPanel">
|
||||
<Button
|
||||
size="$3"
|
||||
chromeless
|
||||
circular
|
||||
disabled={grid.currentPage <= 1}
|
||||
icon={FirstPageIcon ? <FirstPageIcon size={16} /> : undefined}
|
||||
icon={FirstPageIcon ? <FirstPageIcon size="sm" color="$textSecondary" /> : undefined}
|
||||
onPress={() => grid.setPage(1)}
|
||||
/>
|
||||
<Button
|
||||
@@ -227,10 +286,10 @@ export function TableFooter({ visible = true }) {
|
||||
chromeless
|
||||
circular
|
||||
disabled={grid.currentPage <= 1}
|
||||
icon={PreviousPageIcon ? <PreviousPageIcon size={16} /> : undefined}
|
||||
icon={PreviousPageIcon ? <PreviousPageIcon size="sm" color="$textSecondary" /> : undefined}
|
||||
onPress={() => grid.setPage(grid.currentPage - 1)}
|
||||
/>
|
||||
<Text color="$color" opacity={0.75}>
|
||||
<Text color="$textSecondary">
|
||||
Page {grid.currentPage} of {grid.pageCount}
|
||||
</Text>
|
||||
<Button
|
||||
@@ -238,7 +297,7 @@ export function TableFooter({ visible = true }) {
|
||||
chromeless
|
||||
circular
|
||||
disabled={grid.currentPage >= grid.pageCount}
|
||||
icon={NextPageIcon ? <NextPageIcon size={16} /> : undefined}
|
||||
icon={NextPageIcon ? <NextPageIcon size="sm" color="$textSecondary" /> : undefined}
|
||||
onPress={() => grid.setPage(grid.currentPage + 1)}
|
||||
/>
|
||||
<Button
|
||||
@@ -246,11 +305,10 @@ export function TableFooter({ visible = true }) {
|
||||
chromeless
|
||||
circular
|
||||
disabled={grid.currentPage >= grid.pageCount}
|
||||
icon={LastPageIcon ? <LastPageIcon size={16} /> : undefined}
|
||||
icon={LastPageIcon ? <LastPageIcon size="sm" color="$textSecondary" /> : undefined}
|
||||
onPress={() => grid.setPage(grid.pageCount)}
|
||||
/>
|
||||
</XStack>
|
||||
</XStack>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user