feat(card): refine header and inline actions
This commit is contained in:
@@ -1,3 +1,30 @@
|
||||
.card {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 28rem;
|
||||
}
|
||||
.cardActions {
|
||||
position: absolute;
|
||||
top: 4.5rem;
|
||||
right: clamp(1.1rem, 3vw, 1.6rem);
|
||||
padding: 4px;
|
||||
border: 1px solid var(--mantine-color-gray-3);
|
||||
border-radius: var(--mantine-radius-lg);
|
||||
background: var(--mantine-color-white);
|
||||
box-shadow: 0 3px 12px #0000001a;
|
||||
}
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
.cardActions {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.card:hover .cardActions,
|
||||
.card:focus-within .cardActions {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.wrap {
|
||||
overflow-wrap: anywhere;
|
||||
min-width: 0;
|
||||
@@ -21,7 +48,21 @@
|
||||
.operations[data-pending] {
|
||||
opacity: 0.55;
|
||||
}
|
||||
@media (pointer: coarse) {
|
||||
.cardActions button {
|
||||
min-width: 36px;
|
||||
min-height: 36px;
|
||||
}
|
||||
}
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.cardActions {
|
||||
transition: opacity 180ms ease;
|
||||
}
|
||||
.page button {
|
||||
transition:
|
||||
background-color 180ms ease,
|
||||
color 180ms ease;
|
||||
}
|
||||
.operations {
|
||||
animation: operations-appear 200ms ease-out;
|
||||
transition: opacity 150ms ease;
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
Button,
|
||||
Group,
|
||||
Pagination,
|
||||
Paper,
|
||||
Select,
|
||||
Skeleton,
|
||||
Stack,
|
||||
@@ -124,41 +123,76 @@ const CardPageView: FC<CardPageViewProps> = (props) => {
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Stack gap="lg">
|
||||
<Paper p="md" withBorder>
|
||||
<Stack>
|
||||
<Title order={1} className={classes.wrap}>
|
||||
{card.name}
|
||||
</Title>
|
||||
<Stack gap="lg" className={classes.page}>
|
||||
<Stack>
|
||||
<Title order={1} className={classes.wrap}>
|
||||
{card.name}
|
||||
</Title>
|
||||
<div className={classes.card}>
|
||||
<BankCardFace card={card} />
|
||||
<Text c="dimmed">{t("bank.manualBalance")}</Text>
|
||||
<Group>
|
||||
<Button
|
||||
disabled={Boolean(card.archivedAt) || !vm.cards.data}
|
||||
onClick={() => vm.setOperation("new")}
|
||||
<Group gap={4} wrap="nowrap" className={classes.cardActions}>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
size="md"
|
||||
radius="md"
|
||||
aria-label={t("bank.editCard")}
|
||||
title={t("bank.editCard")}
|
||||
onClick={() => vm.setEditingCard(true)}
|
||||
>
|
||||
{t("bank.addOperation")}
|
||||
</Button>
|
||||
<Button variant="default" onClick={() => vm.setEditingCard(true)}>
|
||||
{t("bank.editCard")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="default"
|
||||
<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"
|
||||
size="md"
|
||||
radius="md"
|
||||
color="red"
|
||||
aria-label={t(card.archivedAt ? "bank.restore" : "bank.archive")}
|
||||
title={t(card.archivedAt ? "bank.restore" : "bank.archive")}
|
||||
loading={vm.archivePending}
|
||||
onClick={() => void vm.toggleArchive()}
|
||||
>
|
||||
{t(card.archivedAt ? "bank.restore" : "bank.archive")}
|
||||
</Button>
|
||||
<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={
|
||||
card.archivedAt
|
||||
? "M3 3h18v4H3z M5 7v14h14V7 M12 17V10m-3 3 3-3 3 3"
|
||||
: "M3 6h18 M9 6V3h6v3 M5 6l1 15h12l1-15 M10 10v7 M14 10v7"
|
||||
}
|
||||
/>
|
||||
</svg>
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
{vm.archiveError ? (
|
||||
<BankFeedback
|
||||
error={vm.archiveError}
|
||||
retry={() => void vm.card.refetch()}
|
||||
/>
|
||||
) : null}
|
||||
{card.archivedAt && <Text>{t("bank.archiveHint")}</Text>}
|
||||
</Stack>
|
||||
</Paper>
|
||||
</div>
|
||||
{vm.archiveError ? (
|
||||
<BankFeedback
|
||||
error={vm.archiveError}
|
||||
retry={() => void vm.card.refetch()}
|
||||
/>
|
||||
) : null}
|
||||
{card.archivedAt && <Text>{t("bank.archiveHint")}</Text>}
|
||||
</Stack>
|
||||
{vm.card.isError && (
|
||||
<Group role="alert">
|
||||
<Text>{t("bank.refreshError")}</Text>
|
||||
@@ -185,7 +219,30 @@ const CardPageView: FC<CardPageViewProps> = (props) => {
|
||||
retry={() => void vm.summary.refetch()}
|
||||
/>
|
||||
<Group justify="space-between" align="end">
|
||||
<Title order={2}>{t("bank.operations")}</Title>
|
||||
<Group gap="xs">
|
||||
<Title order={2}>{t("bank.operations")}</Title>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
size="lg"
|
||||
aria-label={t("bank.addOperation")}
|
||||
title={t("bank.addOperation")}
|
||||
disabled={Boolean(card.archivedAt) || !vm.cards.data}
|
||||
onClick={() => vm.setOperation("new")}
|
||||
>
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M12 5v14 M5 12h14" />
|
||||
</svg>
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
<Select
|
||||
label={t("bank.pageSize")}
|
||||
value={vm.pageSize}
|
||||
|
||||
Reference in New Issue
Block a user