feat(cards): show operation icons on hover

This commit is contained in:
2026-09-12 19:29:11 +03:00
parent ff4e88edbe
commit dedfec10d1
3 changed files with 115 additions and 20 deletions
+50 -17
View File
@@ -1,4 +1,5 @@
import { import {
ActionIcon,
Anchor, Anchor,
Badge, Badge,
Button, Button,
@@ -161,24 +162,56 @@ const CardPageView: FC<CardPageViewProps> = (props) => {
</Anchor> </Anchor>
) : null ) : null
} }
actions={
<Group gap={4} wrap="nowrap">
<ActionIcon
variant="subtle"
disabled={!row.editable || Boolean(card.archivedAt)}
size="sm"
aria-label={t("bank.edit")}
title={t("bank.edit")}
onClick={() => vm.setOperation(row.item)}
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<path d="m16 3 5 5-12 12-6 1 1-6L16 3Z M13 6l5 5" />
</svg>
</ActionIcon>
<ActionIcon
variant="subtle"
color="red"
disabled={!row.editable || Boolean(card.archivedAt)}
size="sm"
aria-label={t("bank.delete")}
title={t("bank.delete")}
onClick={() => vm.setDeleting(row.item)}
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<path d="M3 6h18 M9 6V3h6v3 M5 6l1 15h12l1-15 M10 10v7 M14 10v7" />
</svg>
</ActionIcon>
</Group>
}
/> />
<Group mt="xs">
<Button
variant="subtle"
disabled={!row.editable || Boolean(card.archivedAt)}
onClick={() => vm.setOperation(row.item)}
>
{t("bank.edit")}
</Button>
<Button
variant="subtle"
color="red"
disabled={!row.editable || Boolean(card.archivedAt)}
onClick={() => vm.setDeleting(row.item)}
>
{t("bank.delete")}
</Button>
</Group>
</section> </section>
)) ))
) : ( ) : (
+53
View File
@@ -40,3 +40,56 @@
text-align: left; text-align: left;
} }
} }
.valueFooter {
display: grid;
align-items: center;
justify-items: end;
min-height: 28px;
}
.valueLabel,
.actions {
grid-area: 1 / 1;
transition: opacity 180ms ease;
}
.valueLabel {
opacity: 0;
}
.actions {
display: flex;
}
@media (hover: hover) and (pointer: fine) {
.valueLabel {
opacity: 1;
}
.actions {
opacity: 0;
pointer-events: none;
}
.row:hover .actions,
.row:focus-within .actions {
opacity: 1;
pointer-events: auto;
}
.row:hover .valueLabel,
.row:focus-within .valueLabel {
opacity: 0;
}
}
@media (max-width: 36em) {
.valueFooter {
justify-items: start;
}
}
@media (pointer: coarse) {
.actions button {
min-width: 36px;
min-height: 36px;
}
}
@media (prefers-reduced-motion: reduce) {
.valueLabel,
.actions {
transition: none;
}
}
+12 -3
View File
@@ -6,6 +6,7 @@ type EntryCardProps = {
title: string; title: string;
metadata: string; metadata: string;
details?: ReactNode; details?: ReactNode;
actions?: ReactNode;
initial: string; initial: string;
imageSrc?: string | null; imageSrc?: string | null;
value: string; value: string;
@@ -40,9 +41,17 @@ const EntryCard: FC<EntryCardProps> = (props) => {
<Text className={classes[props.tone]} fw={700}> <Text className={classes[props.tone]} fw={700}>
{props.value} {props.value}
</Text> </Text>
<Text className={classes[props.tone]} size="sm"> <div className={props.actions ? classes.valueFooter : undefined}>
{props.valueLabel} <Text
</Text> className={`${classes[props.tone]} ${props.actions ? classes.valueLabel : ""}`}
size="sm"
>
{props.valueLabel}
</Text>
{props.actions && (
<div className={classes.actions}>{props.actions}</div>
)}
</div>
</Stack> </Stack>
</Paper> </Paper>
); );