dns dashboard: mobile usability — zone-name truncate, DataTable text-node DRY fix, 44px tap targets, taller back/close

This commit is contained in:
hanzo-dev
2026-07-18 10:18:07 -07:00
parent ed3300b99d
commit c6f7bb54a3
3 changed files with 45 additions and 32 deletions
+9 -7
View File
@@ -457,9 +457,9 @@ export function DnsModule(_props: { params: Record<string, string> }) {
key: 'name',
header: 'Zone',
render: (z) => (
<XStack items="center" gap="$2">
<XStack items="center" gap="$2" flex={1} minW={0}>
<Globe size={14} color="#9a9a9a" />
<Text fontSize="$3" color="$color12" numberOfLines={1}>{displayZone(z.name)}</Text>
<Text fontSize="$3" color="$color12" numberOfLines={1} flex={1} minW={0}>{displayZone(z.name)}</Text>
</XStack>
),
},
@@ -479,13 +479,15 @@ export function DnsModule(_props: { params: Record<string, string> }) {
{
key: 'actions',
header: '',
width: 92,
width: 108,
align: 'right',
// ≥44px hit areas + a wide Edit↔Delete gap so the destructive Delete is hard
// to mis-tap on touch (the icon glyph stays small; the tap target is 44×44).
render: (r) =>
active ? (
<XStack gap="$1" justify="flex-end">
<Button size="$2" chromeless icon={<Pencil size={15} />} aria-label="Edit record" onPress={() => setDialog({ kind: 'editRecord', zone: active, record: r })} />
<Button size="$2" chromeless icon={<Trash2 size={15} />} aria-label="Delete record" onPress={() => setDialog({ kind: 'deleteRecord', zone: active, record: r })} />
<XStack gap="$3" justify="flex-end">
<Button chromeless width={44} height={44} icon={<Pencil size={15} />} aria-label="Edit record" onPress={() => setDialog({ kind: 'editRecord', zone: active, record: r })} />
<Button chromeless width={44} height={44} icon={<Trash2 size={15} />} aria-label="Delete record" onPress={() => setDialog({ kind: 'deleteRecord', zone: active, record: r })} />
</XStack>
) : null,
},
@@ -496,7 +498,7 @@ export function DnsModule(_props: { params: Record<string, string> }) {
return (
<YStack gap="$4" p="$4">
{inZone ? (
<Button chromeless size="$2" icon={<ArrowLeft size={15} />} self="flex-start" onPress={backToZones}>
<Button chromeless size="$3" minH={44} icon={<ArrowLeft size={15} />} self="flex-start" onPress={backToZones}>
All zones
</Button>
) : null}
+35 -24
View File
@@ -112,30 +112,41 @@ export function DataTable<T>({
cursor={onRowPress ? 'pointer' : undefined}
onPress={onRowPress ? () => onRowPress(row) : undefined}
>
{columns.map((c) => (
<YStack
key={c.key}
width={c.width}
flex={c.width ? undefined : 1}
minW={c.width ? undefined : FLEX_MIN_COL_W}
justify="center"
items={c.align === 'right' ? 'flex-end' : 'flex-start'}
>
{c.render ? (
c.render(row)
) : (
<Text
fontSize="$3"
numberOfLines={1}
color="$color12"
text={c.align === 'right' ? 'right' : 'left'}
className={c.mono ? 'hz-mono' : undefined}
>
{String((row as Record<string, unknown>)[c.key] ?? '')}
</Text>
)}
</YStack>
))}
{columns.map((c) => {
// A cell value is either a column's rendered node or the raw
// `String(row[key])`. Whenever it resolves to a PRIMITIVE
// (string|number) — the default cell OR a `render()` that returns
// `String(count)` / `ttl` / `priority` — it is wrapped in the ONE
// styled Text below, so the primitive can never emit a bare text
// node (illegal under a native View → RN red-box + console spam)
// and always picks up the numeric mono/align styling. Element
// renders (Text/badges/cells) pass through untouched.
const cell = c.render ? c.render(row) : String((row as Record<string, unknown>)[c.key] ?? '')
return (
<YStack
key={c.key}
width={c.width}
flex={c.width ? undefined : 1}
minW={c.width ? undefined : FLEX_MIN_COL_W}
justify="center"
items={c.align === 'right' ? 'flex-end' : 'flex-start'}
>
{typeof cell === 'string' || typeof cell === 'number' ? (
<Text
fontSize="$3"
numberOfLines={1}
color="$color12"
text={c.align === 'right' ? 'right' : 'left'}
className={c.mono ? 'hz-mono' : undefined}
>
{cell}
</Text>
) : (
cell
)}
</YStack>
)
})}
</XStack>
))}
</YStack>
+1 -1
View File
@@ -186,7 +186,7 @@ export function SlideOver({
{title}
</Text>
{headerRight}
<Button size="$2" chromeless icon={<X size={18} />} onPress={onClose} aria-label="Close" />
<Button chromeless width={44} height={44} icon={<X size={18} />} onPress={onClose} aria-label="Close" />
</XStack>
<ScrollView flex={1}>
<YStack flex={1} p="$4" gap="$3">