@sincpro/web-ui 0.1.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -13
- package/dist/components/activity/Activity.d.ts +69 -0
- package/dist/components/activity/Activity.js +279 -0
- package/dist/components/activity/ActivityBadge.d.ts +16 -0
- package/dist/components/activity/ActivityBadge.js +29 -0
- package/dist/components/{chatter → activity}/Messages.d.ts +2 -2
- package/dist/components/{chatter → activity}/Messages.js +9 -6
- package/dist/components/activity/index.d.ts +4 -0
- package/dist/components/activity/index.js +5 -0
- package/dist/components/assist/AssistMark.d.ts +3 -3
- package/dist/components/assist/AssistMark.js +1 -1
- package/dist/components/assist/assist.d.ts +1 -1
- package/dist/components/assist/assist.js +1 -1
- package/dist/components/calendar/RecordCalendar.d.ts +48 -0
- package/dist/components/calendar/RecordCalendar.js +101 -0
- package/dist/components/calendar/index.d.ts +1 -0
- package/dist/components/calendar/index.js +1 -0
- package/dist/components/form/Sheet.d.ts +4 -0
- package/dist/components/form/Sheet.js +16 -5
- package/dist/components/gantt/RecordGantt.d.ts +44 -0
- package/dist/components/gantt/RecordGantt.js +110 -0
- package/dist/components/gantt/index.d.ts +1 -0
- package/dist/components/gantt/index.js +1 -0
- package/dist/components/index.d.ts +4 -1
- package/dist/components/index.js +4 -1
- package/dist/components/lines/LineItems.d.ts +45 -5
- package/dist/components/lines/LineItems.js +106 -18
- package/dist/components/listing/BoardColumns.d.ts +39 -0
- package/dist/components/listing/BoardColumns.js +138 -0
- package/dist/components/listing/Listing.js +1 -1
- package/dist/components/listing/ListingBar.d.ts +46 -2
- package/dist/components/listing/ListingBar.js +47 -5
- package/dist/components/listing/PivotExplorer.d.ts +34 -0
- package/dist/components/listing/PivotExplorer.js +86 -0
- package/dist/components/listing/RecordCard.d.ts +6 -8
- package/dist/components/listing/RecordCard.js +13 -7
- package/dist/components/listing/Row.d.ts +25 -2
- package/dist/components/listing/Row.js +30 -13
- package/dist/components/listing/index.d.ts +2 -0
- package/dist/components/listing/index.js +2 -0
- package/dist/components/listing/set.d.ts +1 -1
- package/dist/components/navigation/index.d.ts +0 -2
- package/dist/components/navigation/index.js +2 -3
- package/dist/components/record/RecordAudit.d.ts +17 -0
- package/dist/components/record/RecordAudit.js +27 -0
- package/dist/components/record/RecordHeader.d.ts +27 -17
- package/dist/components/record/RecordHeader.js +64 -20
- package/dist/components/record/RecordNotices.d.ts +37 -0
- package/dist/components/record/RecordNotices.js +75 -0
- package/dist/components/record/RecordRibbon.d.ts +13 -0
- package/dist/components/record/RecordRibbon.js +22 -0
- package/dist/components/record/ThumbBar.js +1 -1
- package/dist/components/record/index.d.ts +2 -0
- package/dist/components/record/index.js +2 -0
- package/dist/components/search/CriteriaChips.d.ts +6 -1
- package/dist/components/search/CriteriaChips.js +71 -20
- package/dist/components/search/SearchBar.d.ts +4 -1
- package/dist/components/search/SearchBar.js +15 -7
- package/dist/components/timeline/RecordTimeline.d.ts +60 -0
- package/dist/components/timeline/RecordTimeline.js +214 -0
- package/dist/components/timeline/TimelineToolbar.d.ts +30 -0
- package/dist/components/timeline/TimelineToolbar.js +31 -0
- package/dist/components/timeline/index.d.ts +3 -0
- package/dist/components/timeline/index.js +3 -0
- package/dist/components/timeline/time.d.ts +43 -0
- package/dist/components/timeline/time.js +135 -0
- package/dist/foundations/copy.d.ts +194 -18
- package/dist/foundations/copy.es.js +142 -15
- package/dist/foundations/copy.js +133 -11
- package/dist/foundations/index.d.ts +1 -1
- package/dist/foundations/match.d.ts +23 -0
- package/dist/foundations/match.js +63 -0
- package/dist/foundations/slots.d.ts +1 -1
- package/dist/foundations/slots.js +8 -3
- package/dist/foundations/types.d.ts +7 -0
- package/dist/foundations/types.js +0 -36
- package/dist/primitives/block/index.d.ts +1 -1
- package/dist/primitives/block/index.js +1 -1
- package/dist/primitives/cell/CellStack.d.ts +7 -1
- package/dist/primitives/cell/CellStack.js +5 -2
- package/dist/primitives/choice/ComboBox.d.ts +3 -0
- package/dist/primitives/choice/ComboBox.js +10 -6
- package/dist/primitives/choice/KanbanState.d.ts +14 -0
- package/dist/primitives/choice/KanbanState.js +37 -0
- package/dist/primitives/choice/Priority.d.ts +20 -0
- package/dist/primitives/choice/Priority.js +41 -0
- package/dist/primitives/choice/SearchField.d.ts +10 -1
- package/dist/primitives/choice/SearchField.js +21 -2
- package/dist/primitives/choice/Slider.d.ts +6 -1
- package/dist/primitives/choice/Slider.js +2 -2
- package/dist/primitives/choice/index.d.ts +2 -0
- package/dist/primitives/choice/index.js +2 -0
- package/dist/primitives/date/Calendar.js +30 -6
- package/dist/primitives/date/DatePicker.d.ts +11 -3
- package/dist/primitives/date/DatePicker.js +12 -8
- package/dist/primitives/date/DateTime.d.ts +35 -0
- package/dist/primitives/date/DateTime.js +100 -0
- package/dist/primitives/date/index.d.ts +1 -0
- package/dist/primitives/date/index.js +4 -2
- package/dist/primitives/dialog/Dialog.d.ts +9 -3
- package/dist/primitives/dialog/Dialog.js +41 -5
- package/dist/primitives/field/DropZone.js +1 -1
- package/dist/primitives/field/Field.d.ts +14 -2
- package/dist/primitives/field/Field.js +25 -14
- package/dist/primitives/field/copy.d.ts +16 -0
- package/dist/primitives/field/copy.js +69 -0
- package/dist/primitives/field/index.d.ts +2 -0
- package/dist/primitives/field/index.js +4 -0
- package/dist/primitives/field/note.d.ts +55 -0
- package/dist/primitives/field/note.js +117 -0
- package/dist/primitives/index.d.ts +1 -0
- package/dist/primitives/index.js +1 -0
- package/dist/primitives/menu/Menu.d.ts +28 -0
- package/dist/primitives/menu/Menu.js +47 -17
- package/dist/primitives/notice/Callout.js +1 -1
- package/dist/primitives/notice/Flag.d.ts +13 -0
- package/dist/primitives/notice/Flag.js +31 -0
- package/dist/primitives/notice/index.d.ts +2 -1
- package/dist/primitives/notice/index.js +2 -1
- package/dist/primitives/overlay/Preview.d.ts +19 -0
- package/dist/primitives/overlay/Preview.js +69 -0
- package/dist/primitives/overlay/index.d.ts +1 -0
- package/dist/primitives/overlay/index.js +3 -1
- package/dist/primitives/picker/MatchedText.d.ts +10 -0
- package/dist/primitives/picker/MatchedText.js +12 -0
- package/dist/primitives/picker/Picker.d.ts +67 -4
- package/dist/primitives/picker/Picker.js +142 -23
- package/dist/primitives/table/CellCopy.d.ts +20 -0
- package/dist/primitives/table/CellCopy.js +23 -0
- package/dist/primitives/table/DataTable.d.ts +55 -1
- package/dist/primitives/table/DataTable.js +126 -40
- package/dist/primitives/table/EditableTable.d.ts +102 -2
- package/dist/primitives/table/EditableTable.js +287 -32
- package/dist/primitives/table/ReportTable.js +2 -1
- package/dist/primitives/table/RowActions.js +7 -2
- package/dist/primitives/table/SheetTable.d.ts +4 -1
- package/dist/primitives/table/SheetTable.js +38 -5
- package/dist/primitives/table/Table.d.ts +5 -0
- package/dist/primitives/table/Table.js +17 -8
- package/dist/primitives/table/index.d.ts +2 -2
- package/dist/primitives/table/index.js +1 -1
- package/dist/primitives/tabs/Tabs.d.ts +2 -2
- package/dist/primitives/tabs/Tabs.js +1 -1
- package/dist/primitives/tag/Tags.d.ts +2 -0
- package/dist/primitives/tag/Tags.js +2 -1
- package/dist/primitives/tag/TagsField.d.ts +13 -0
- package/dist/primitives/tag/TagsField.js +39 -10
- package/dist/primitives/tag/choices.d.ts +2 -0
- package/dist/primitives/tree/Tree.d.ts +56 -0
- package/dist/primitives/tree/Tree.js +74 -0
- package/dist/primitives/tree/index.d.ts +1 -0
- package/dist/primitives/tree/index.js +3 -0
- package/dist/primitives/value/Avatar.d.ts +16 -0
- package/dist/primitives/value/Avatar.js +91 -0
- package/dist/primitives/value/Copyable.d.ts +11 -0
- package/dist/primitives/value/Copyable.js +27 -6
- package/dist/primitives/value/Fecha.js +10 -1
- package/dist/primitives/value/Person.d.ts +6 -51
- package/dist/primitives/value/Person.js +9 -148
- package/dist/primitives/value/ProgressBar.d.ts +31 -0
- package/dist/primitives/value/ProgressBar.js +53 -0
- package/dist/primitives/value/Relation.d.ts +22 -5
- package/dist/primitives/value/Relation.js +42 -14
- package/dist/primitives/value/Totals.d.ts +102 -7
- package/dist/primitives/value/Totals.js +52 -2
- package/dist/primitives/value/Value.d.ts +0 -37
- package/dist/primitives/value/Value.js +0 -39
- package/dist/primitives/value/contact.d.ts +30 -0
- package/dist/primitives/value/contact.js +51 -0
- package/dist/primitives/value/index.d.ts +5 -4
- package/dist/primitives/value/index.js +5 -4
- package/dist/tokens.css +179 -7
- package/dist/views/list/List.d.ts +32 -3
- package/dist/views/list/List.js +51 -7
- package/dist/views/list/ListView.d.ts +1 -1
- package/dist/views/list/ListView.js +70 -13
- package/dist/views/list/columns.js +8 -1
- package/dist/views/list/derive.d.ts +12 -1
- package/dist/views/list/derive.js +29 -3
- package/dist/views/list/presentations.d.ts +5 -2
- package/dist/views/list/presentations.js +35 -3
- package/dist/views/list/types.d.ts +57 -1
- package/dist/views/list/useNarrowList.d.ts +10 -0
- package/dist/views/list/useNarrowList.js +20 -6
- package/dist/views/record/Record.d.ts +41 -8
- package/dist/views/record/Record.js +113 -28
- package/dist/views/record/index.d.ts +1 -1
- package/package.json +15 -4
- package/dist/components/chatter/Chatter.d.ts +0 -161
- package/dist/components/chatter/Chatter.js +0 -232
- package/dist/components/chatter/index.d.ts +0 -3
- package/dist/components/chatter/index.js +0 -4
- package/dist/components/navigation/CommandPalette.d.ts +0 -40
- package/dist/components/navigation/CommandPalette.js +0 -114
- package/dist/components/navigation/commands.d.ts +0 -30
- package/dist/components/navigation/commands.js +0 -30
- /package/dist/components/{chatter → activity}/Timeline.d.ts +0 -0
- /package/dist/components/{chatter → activity}/Timeline.js +0 -0
|
@@ -6,9 +6,13 @@ import { Button, ButtonLink, IconButton } from "../button/Button.js";
|
|
|
6
6
|
import { CellStack, Thumb } from "../cell/CellStack.js";
|
|
7
7
|
import { FieldIdsContext } from "../field/context.js";
|
|
8
8
|
import { useFormMode } from "../field/mode.js";
|
|
9
|
+
import { Menu } from "../menu/Menu.js";
|
|
9
10
|
import { PopoverTrigger } from "../overlay/PopoverTrigger.js";
|
|
11
|
+
import { Preview } from "../overlay/Preview.js";
|
|
10
12
|
import { Tag } from "../tag/Tag.js";
|
|
11
|
-
import {
|
|
13
|
+
import { Avatar } from "./Avatar.js";
|
|
14
|
+
import { ContactRows } from "./contact.js";
|
|
15
|
+
import { ArrowRight, Ellipsis, Info } from "lucide-react";
|
|
12
16
|
import { useContext } from "react";
|
|
13
17
|
import { Link } from "react-aria-components";
|
|
14
18
|
/**
|
|
@@ -17,22 +21,27 @@ import { Link } from "react-aria-components";
|
|
|
17
21
|
* Context: it answers the question a person has about the customer of an invoice without
|
|
18
22
|
* leaving the invoice — «does it owe us?», «what is its NIT?» — and, when the answer is
|
|
19
23
|
* «I need more», one press takes them to the record. It is what floats from a
|
|
20
|
-
* `RelationValue
|
|
24
|
+
* `RelationValue` and what opens from a `UserChip` (a person is a related record too: round
|
|
25
|
+
* face, role, the ways to reach them), and it is exported on its own for a side panel.
|
|
21
26
|
*/
|
|
22
|
-
export function RelationCard({ name, secondary, media, summary = {}, onOpen, href, }) {
|
|
27
|
+
export function RelationCard({ name, secondary, media, summary = {}, contact, onOpen, href, }) {
|
|
23
28
|
const copy = useUiCopy();
|
|
24
29
|
const title = summary.title ?? name;
|
|
25
30
|
return (
|
|
26
31
|
/* React events bubble through the popover's portal to the React parents of the link —
|
|
27
32
|
a table row that opens on click among them. A click inside the card stays here. */
|
|
28
|
-
_jsxs("div", { className: "flex w-72 max-w-full flex-col gap-3", onClick: (e) => e.stopPropagation(), children: [_jsxs("div", { className: "flex min-w-0 items-start gap-3", children: [media === undefined ? null :
|
|
33
|
+
_jsxs("div", { className: "flex w-72 max-w-full flex-col gap-3", onClick: (e) => e.stopPropagation(), children: [_jsxs("div", { className: "flex min-w-0 items-start gap-3", children: [media === undefined ? null : media.person === true ? (
|
|
34
|
+
/* The name is written beside it: the photo's `alt` would say it twice. */
|
|
35
|
+
_jsx("span", { "aria-hidden": true, className: "flex shrink-0", children: _jsx(Avatar, { name: name, size: "xl", ...(media.src === undefined ? {} : { src: media.src }) }) })) : (_jsx(Thumb, { size: "lg", ...(media.src === undefined ? {} : { src: media.src }), ...(media.alt === undefined ? {} : { alt: media.alt }), ...(media.icon === undefined ? {} : { icon: media.icon }) })), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [_jsx("span", { className: "text-lead leading-tight font-semibold text-ink", children: title }), secondary === undefined ? null : (_jsx("span", { className: "truncate text-micro tabular-nums text-muted", children: secondary })), summary.tag === undefined ? null : (_jsx("span", { className: "flex", children: _jsx(Tag, { tone: summary.tag.tone ?? "neutral", children: summary.tag.label }) }))] })] }), summary.facts === undefined || summary.facts.length === 0 ? null : (_jsx("dl", { className: "grid grid-cols-2 gap-x-4 gap-y-2 border-t border-line pt-3", children: summary.facts.map((fact) => (_jsxs("div", { className: "flex min-w-0 flex-col", children: [_jsx("dt", { className: "truncate text-micro text-muted", children: fact.label }), _jsx("dd", { className: "truncate text-meta tabular-nums text-ink", children: fact.value })] }, fact.label))) })), contact === undefined ? null : _jsx(ContactRows, { contact: contact }), href !== undefined ? (_jsx("div", { className: "flex justify-end border-t border-line pt-2", children: _jsx(ButtonLink, { href: href, role: "ghost", size: "sm", icon: ArrowRight, ...(onOpen === undefined ? {} : { onClick: onOpen }), children: copy.relation.open }) })) : onOpen !== undefined ? (_jsx("div", { className: "flex justify-end border-t border-line pt-2", children: _jsx(Button, { role: "ghost", size: "sm", icon: ArrowRight, onClick: onOpen, children: copy.relation.open }) })) : null] }));
|
|
29
36
|
}
|
|
30
37
|
/**
|
|
31
38
|
* A many2one as a value: the related record's name as a link, with its secondary line and
|
|
32
39
|
* picture, and its summary one press away. Works in a table cell, a list row, a `Record.Facts`
|
|
33
40
|
* value and a form field (read mode; pass the `Picker` as `editor` for edit mode).
|
|
34
41
|
*/
|
|
35
|
-
export function RelationValue({ name, secondary, media, onOpen, href, summary, editor, mode, }) {
|
|
42
|
+
export function RelationValue({ name, secondary, media, onOpen: onOpenOwn, onOpenIn, openTargets = ["modal", "peek", "side", "page"], href, summary, preview, editor, mode, }) {
|
|
43
|
+
const first = openTargets[0];
|
|
44
|
+
const onOpen = onOpenIn !== undefined && first !== undefined ? () => onOpenIn(first) : onOpenOwn;
|
|
36
45
|
const copy = useUiCopy();
|
|
37
46
|
const formMode = useFormMode(mode);
|
|
38
47
|
/* Inside a `Field` the value takes the control's height, so a form that goes from
|
|
@@ -45,13 +54,32 @@ export function RelationValue({ name, secondary, media, onOpen, href, summary, e
|
|
|
45
54
|
return frame(_jsx("span", { className: "text-meta text-muted italic", children: copy.relation.none }));
|
|
46
55
|
}
|
|
47
56
|
const goes = href !== undefined || onOpen !== undefined;
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
const summaryLabel = fillCopy(copy.relationCard.summary, { name });
|
|
58
|
+
const card = (open) => (_jsx(RelationCard, { name: name, ...(summary === undefined ? {} : { summary }), ...(secondary === undefined ? {} : { secondary }), ...(media === undefined ? {} : { media }), ...(href === undefined ? {} : { href }), ...(open === undefined ? {} : { onOpen: open }) }));
|
|
59
|
+
/* What floats from the name: the card, or the consumer's own content. */
|
|
60
|
+
const previewed = preview === false || preview === null
|
|
61
|
+
? undefined
|
|
62
|
+
: preview === undefined
|
|
63
|
+
? summary === undefined
|
|
64
|
+
? undefined
|
|
65
|
+
: card(onOpen)
|
|
66
|
+
: preview === true
|
|
67
|
+
? card(onOpen)
|
|
68
|
+
: preview;
|
|
69
|
+
const link = goes ? (_jsx(Link, { ...(href === undefined ? {} : { href }), ...(onOpen === undefined ? {} : { onPress: onOpen }), className: ({ isFocusVisible }) => cn("cursor-pointer rounded-mark text-primary no-underline underline-offset-2 hover:underline", focusRing({ isFocusVisible })), children: name })) : undefined;
|
|
70
|
+
const primary = link === undefined ? (name) : previewed === undefined ? (link) : (_jsx(Preview, { trigger: link, label: summaryLabel, children: previewed }));
|
|
71
|
+
/* The `i` only when the name is not a link: there is nothing for the card to ride on. */
|
|
72
|
+
const info = previewed === undefined || goes ? undefined : () => previewed;
|
|
73
|
+
return frame(_jsxs("span", { className: "inline-flex max-w-full min-w-0 items-center gap-1", children: [_jsx(CellStack, { primary: primary, ...(secondary === undefined ? {} : { secondary }), ...(media === undefined
|
|
74
|
+
? {}
|
|
75
|
+
: media.person === true
|
|
76
|
+
? { avatar: { name, ...(media.src === undefined ? {} : { src: media.src }) } }
|
|
77
|
+
: { media }) }), onOpenIn === undefined || openTargets.length < 2 ? null : (_jsx(Menu, { label: copy.picker.openMore, icon: Ellipsis, iconOnly: true, chevron: false, variant: "text", groups: [
|
|
78
|
+
{
|
|
79
|
+
options: openTargets.slice(1).map((to) => ({
|
|
80
|
+
id: to,
|
|
81
|
+
label: copy.picker.openIn[to],
|
|
82
|
+
})),
|
|
83
|
+
},
|
|
84
|
+
], onSelect: (to) => onOpenIn(to) })), info === undefined ? null : (_jsx(PopoverTrigger, { label: summaryLabel, trigger: _jsx(IconButton, { icon: Info, label: summaryLabel, size: "sm" }), children: info }))] }));
|
|
57
85
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
2
|
/**
|
|
3
3
|
* The money summary of a document — subtotal, taxes, discounts and the TOTAL — under its
|
|
4
|
-
* lines, aligned to the right.
|
|
4
|
+
* lines, aligned to the right. And, once the document starts being settled, what was already
|
|
5
|
+
* taken off it, what is still owed, and what could still be taken off.
|
|
5
6
|
*
|
|
6
7
|
* Context: written as two more rows of the lines table, «IVA» and «Total» were the same size
|
|
7
8
|
* and weight as a product line, and the one number the document exists for had to be found.
|
|
@@ -10,17 +11,111 @@ import type { ReactNode } from "react";
|
|
|
10
11
|
* read (Jakob's law).
|
|
11
12
|
*
|
|
12
13
|
* Labels left and figures right of a narrow column, both in tabular figures, so the amounts
|
|
13
|
-
* line up digit by digit. The `total` row closes the
|
|
14
|
-
*
|
|
14
|
+
* line up digit by digit. The `total` row closes the breakdown: a rule above it.
|
|
15
|
+
*
|
|
16
|
+
* ── Deductions, balance and credits: one block, not three ────────────
|
|
17
|
+
*
|
|
18
|
+
* A posted invoice is read as one sum: the total, minus each payment, gives what is owed. Drawn
|
|
19
|
+
* as a second component under this one, the payments would be a second column of figures that
|
|
20
|
+
* the screen has to line up with the first by hand — which a template cannot do without
|
|
21
|
+
* styling — and the decision below, WHICH figure leads, needs to see the three at once. So it
|
|
22
|
+
* is this block, extended, and a block with only `rows` draws exactly what it drew before.
|
|
23
|
+
*
|
|
24
|
+
* It still does not know what an invoice is. It speaks of `deductions` (a payment, a credit
|
|
25
|
+
* note, a retention: anything already applied against the total), a `balance` (what remains
|
|
26
|
+
* after them), `settled` (the word for «nothing remains»: «Pagada», «Conciliado») and
|
|
27
|
+
* `credits` (what could still be applied). The domain formats the money and owns the actions;
|
|
28
|
+
* this only lays them out and names them.
|
|
29
|
+
*
|
|
30
|
+
* ── The figure that leads moves with the state ───────────────────────
|
|
31
|
+
*
|
|
32
|
+
* There is ONE figure at the title size, and which one it is depends on where the document is:
|
|
33
|
+
*
|
|
34
|
+
* nothing applied yet (a draft, a posted invoice) the total
|
|
35
|
+
* something applied, something owed the balance — the total steps down
|
|
36
|
+
* settled the total, with the `settled` mark
|
|
37
|
+
*
|
|
38
|
+
* Once a payment is in, the question the person brings is «how much is left?», and a total
|
|
39
|
+
* at the title size next to a balance at the body size answers the wrong question first.
|
|
40
|
+
* That is the default; `lead` says it explicitly when the screen knows better.
|
|
41
|
+
*
|
|
42
|
+
* ── A deduction is HEARD as one: «Pagado el 12/10/2026, menos Bs. 1.000,00» ──
|
|
43
|
+
*
|
|
44
|
+
* The amount arrives formatted and WITHOUT a sign; the block writes the minus. The visible
|
|
45
|
+
* «−» is hidden from assistive technology — screen readers skip it or say «dash» — and a
|
|
46
|
+
* visually hidden «menos» (`copy.totals.less`) is read in its place, so the label and the
|
|
47
|
+
* figure are one sentence and not two loose numbers.
|
|
48
|
+
*
|
|
49
|
+
* ── The detail of a deduction: an `i`, asked for ─────────────────────
|
|
50
|
+
*
|
|
51
|
+
* Where it came from (journal, reference, method), going to it, and unlinking it open from an
|
|
52
|
+
* `i` before the label — `PopoverTrigger`, the same `i` a `RelationValue` uses for its
|
|
53
|
+
* summary. Not a `Preview` on hover: the card holds an action that changes the document
|
|
54
|
+
* («Desvincular»), and that is asked for with a press, which the mouse, the keyboard and the
|
|
55
|
+
* finger all have. The `i` sits BEFORE the label so the label and its amount stay together
|
|
56
|
+
* in the reading order.
|
|
57
|
+
*
|
|
58
|
+
* ── None of these actions is the primary ─────────────────────────────
|
|
59
|
+
*
|
|
60
|
+
* The screen's one primary is somewhere else («Registrar pago», «Confirmar»). Unlinking is
|
|
61
|
+
* `secondary` and not `danger`: it does not destroy — the payment comes back as a credit and
|
|
62
|
+
* is applied again with one press, which is the undo. «Aplicar» and «Abrir» are `ghost`.
|
|
15
63
|
*/
|
|
16
64
|
export type TotalsRow = {
|
|
17
65
|
label: ReactNode;
|
|
18
66
|
value: ReactNode;
|
|
19
67
|
/** A note under the label — «incluido en el precio», «sobre Bs. 3.601,80». */
|
|
20
68
|
detail?: ReactNode;
|
|
21
|
-
/** The row
|
|
69
|
+
/** The row that closes the breakdown. Usually one, and last. */
|
|
22
70
|
total?: boolean;
|
|
23
71
|
};
|
|
24
|
-
|
|
72
|
+
/** A fact of a deduction's detail: «Diario · Banco BNB», «Referencia · TRF-88213». */
|
|
73
|
+
export type TotalsFact = {
|
|
74
|
+
label: string;
|
|
75
|
+
value: ReactNode;
|
|
76
|
+
};
|
|
77
|
+
/** Something already applied against the total: a payment, a credit note, a retention. */
|
|
78
|
+
export type TotalsDeduction = {
|
|
79
|
+
/** What it is, as one phrase — «Pagado el 12/10/2026». A string: it also names the `i`
|
|
80
|
+
* and the floating detail. */
|
|
81
|
+
label: string;
|
|
82
|
+
/** The amount taken off, formatted and WITHOUT a sign: the block writes the minus. */
|
|
83
|
+
value: ReactNode;
|
|
84
|
+
/** A quiet note under the label — «Transferencia · Banco BNB». */
|
|
85
|
+
detail?: ReactNode;
|
|
86
|
+
/** What the `i` shows: two to five facts — journal, reference, method. */
|
|
87
|
+
facts?: readonly TotalsFact[];
|
|
88
|
+
/** Go to the applied record. The detail shows «Abrir». */
|
|
89
|
+
onOpen?: () => void;
|
|
90
|
+
/** Undo the application — «Desvincular». Present = allowed; absent = not offered. */
|
|
91
|
+
onRemove?: () => void;
|
|
92
|
+
};
|
|
93
|
+
/** Something that COULD be applied and is not yet: an unused credit of the same party. */
|
|
94
|
+
export type TotalsCredit = {
|
|
95
|
+
/** What it is — «NC/2026/0012». A string: it also names its «Aplicar». */
|
|
96
|
+
label: string;
|
|
97
|
+
/** The amount available, formatted. */
|
|
98
|
+
value: ReactNode;
|
|
99
|
+
/** A quiet note — «del 02/10/2026», «anticipo». */
|
|
100
|
+
detail?: ReactNode;
|
|
101
|
+
onApply: () => void;
|
|
102
|
+
};
|
|
103
|
+
export type TotalsProps = {
|
|
104
|
+
/** The breakdown and its `total`: subtotal, each tax group, discounts, TOTAL. */
|
|
25
105
|
rows: readonly TotalsRow[];
|
|
26
|
-
|
|
106
|
+
/** What was already applied against the total, one row each, after it. */
|
|
107
|
+
deductions?: readonly TotalsDeduction[];
|
|
108
|
+
/** What remains once the deductions are taken off — «Importe adeudado». */
|
|
109
|
+
balance?: Omit<TotalsRow, "total">;
|
|
110
|
+
/** Nothing remains: the word for it, drawn as a mark next to the leading figure —
|
|
111
|
+
* «Pagada». */
|
|
112
|
+
settled?: string;
|
|
113
|
+
/** Which figure leads, at the title size. By default the balance when something was
|
|
114
|
+
* deducted and the document is not settled; otherwise the total. */
|
|
115
|
+
lead?: "total" | "balance";
|
|
116
|
+
/** What could still be applied, apart and under the sum: it is not part of it. */
|
|
117
|
+
credits?: readonly TotalsCredit[];
|
|
118
|
+
/** The title of the credits. `copy.totals.credits` by default. */
|
|
119
|
+
creditsLabel?: string;
|
|
120
|
+
};
|
|
121
|
+
export declare function Totals({ rows, deductions, balance, settled, lead, credits, creditsLabel, }: TotalsProps): import("react").JSX.Element;
|
|
@@ -1,4 +1,54 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
|
|
3
|
-
|
|
3
|
+
import { fillCopy, useUiCopy } from "../../foundations/copy.js";
|
|
4
|
+
import { Button, IconButton } from "../button/Button.js";
|
|
5
|
+
import { PopoverTrigger } from "../overlay/PopoverTrigger.js";
|
|
6
|
+
import { Tag } from "../tag/Tag.js";
|
|
7
|
+
import { ArrowRight, Info, Plus, Unlink } from "lucide-react";
|
|
8
|
+
import { useId } from "react";
|
|
9
|
+
/** The figure the block exists for: a rule above it and the figure at the title size. The
|
|
10
|
+
* `data-lead` marks it for the tests and for whoever reads the DOM. */
|
|
11
|
+
function LeadRow({ row, mark }) {
|
|
12
|
+
return (_jsxs("div", { "data-lead": "", className: "mt-1 flex flex-wrap items-baseline justify-between gap-x-4 border-t border-line-strong pt-2.5", children: [_jsxs("dt", { className: "flex min-w-0 flex-col", children: [_jsxs("span", { className: "flex flex-wrap items-center gap-2 text-body font-semibold text-ink", children: [row.label, mark === undefined ? null : _jsx(Tag, { tone: "good", children: mark })] }), row.detail === undefined ? null : (_jsx("span", { className: "text-micro text-muted", children: row.detail }))] }), _jsx("dd", { className: "ml-auto text-title font-semibold whitespace-nowrap tabular-nums text-ink", children: row.value })] }));
|
|
13
|
+
}
|
|
14
|
+
/** A figure that closes a part of the sum without leading it: the total once the balance
|
|
15
|
+
* leads, or the balance while the total does. Ruled, in the body size. */
|
|
16
|
+
function ClosingRow({ row }) {
|
|
17
|
+
return (_jsxs("div", { className: "mt-1 flex flex-wrap items-baseline justify-between gap-x-4 border-t border-line pt-2", children: [_jsxs("dt", { className: "flex min-w-0 flex-col", children: [_jsx("span", { className: "text-body font-semibold text-ink", children: row.label }), row.detail === undefined ? null : (_jsx("span", { className: "text-micro text-muted", children: row.detail }))] }), _jsx("dd", { className: "ml-auto text-body font-semibold whitespace-nowrap tabular-nums text-ink", children: row.value })] }));
|
|
18
|
+
}
|
|
19
|
+
function PlainRow({ row }) {
|
|
20
|
+
return (_jsxs("div", { className: "flex items-baseline justify-between gap-4", children: [_jsxs("dt", { className: "flex min-w-0 flex-col text-meta text-muted", children: [row.label, row.detail === undefined ? null : (_jsx("span", { className: "text-micro text-muted", children: row.detail }))] }), _jsx("dd", { className: "text-meta whitespace-nowrap tabular-nums text-ink", children: row.value })] }));
|
|
21
|
+
}
|
|
22
|
+
/** What floats from a deduction's `i`: its facts and, when allowed, «Desvincular» and
|
|
23
|
+
* «Abrir». Both close the card first — the row may be gone once the action runs. */
|
|
24
|
+
function DeductionDetail({ item, close }) {
|
|
25
|
+
const copy = useUiCopy();
|
|
26
|
+
const facts = item.facts ?? [];
|
|
27
|
+
const run = (action) => () => {
|
|
28
|
+
close();
|
|
29
|
+
action();
|
|
30
|
+
};
|
|
31
|
+
return (_jsxs("div", { className: "flex w-64 max-w-full flex-col gap-3", children: [facts.length === 0 ? null : (_jsx("dl", { className: "grid grid-cols-2 gap-x-4 gap-y-2", children: facts.map((fact) => (_jsxs("div", { className: "flex min-w-0 flex-col", children: [_jsx("dt", { className: "truncate text-micro text-muted", children: fact.label }), _jsx("dd", { className: "text-meta tabular-nums break-words text-ink", children: fact.value })] }, fact.label))) })), item.onRemove === undefined && item.onOpen === undefined ? null : (_jsxs("div", { className: "flex flex-wrap justify-end gap-2 border-t border-line pt-2", children: [item.onRemove === undefined ? null : (_jsx(Button, { role: "secondary", size: "sm", icon: Unlink, onClick: run(item.onRemove), children: copy.totals.unlink })), item.onOpen === undefined ? null : (_jsx(Button, { role: "ghost", size: "sm", icon: ArrowRight, onClick: run(item.onOpen), children: copy.totals.open }))] }))] }));
|
|
32
|
+
}
|
|
33
|
+
function DeductionRow({ item }) {
|
|
34
|
+
const copy = useUiCopy();
|
|
35
|
+
const asks = (item.facts !== undefined && item.facts.length > 0) ||
|
|
36
|
+
item.onRemove !== undefined ||
|
|
37
|
+
item.onOpen !== undefined;
|
|
38
|
+
return (_jsxs("div", { className: "flex items-baseline justify-between gap-4", children: [_jsxs("dt", { className: "flex min-w-0 items-baseline gap-1 text-meta text-muted", children: [asks ? (_jsx(PopoverTrigger, { title: item.label, placement: "bottom end", trigger: _jsx(IconButton, { icon: Info, label: fillCopy(copy.totals.details, { label: item.label }) }), children: (close) => _jsx(DeductionDetail, { item: item, close: close }) })) : null, _jsxs("span", { className: "flex min-w-0 flex-col", children: [item.label, item.detail === undefined ? null : (_jsx("span", { className: "text-micro text-muted", children: item.detail }))] })] }), _jsxs("dd", { className: "text-meta whitespace-nowrap tabular-nums text-ink", children: [_jsx("span", { "aria-hidden": true, children: "− " }), _jsxs("span", { className: "sr-only", children: [copy.totals.less, " "] }), item.value] })] }));
|
|
39
|
+
}
|
|
40
|
+
function Credits({ items, label }) {
|
|
41
|
+
const copy = useUiCopy();
|
|
42
|
+
const id = useId();
|
|
43
|
+
return (_jsxs("div", { className: "flex flex-col gap-1.5 border-t border-line pt-3", children: [_jsx("p", { id: id, className: "text-micro font-semibold text-muted", children: label }), _jsx("ul", { "aria-labelledby": id, className: "flex flex-col gap-1", children: items.map((credit, i) => (_jsxs("li", { className: "flex flex-wrap items-center justify-between gap-x-3 gap-y-1", children: [_jsxs("span", { className: "flex min-w-0 flex-col", children: [_jsx("span", { className: "text-meta text-ink", children: credit.label }), credit.detail === undefined ? null : (_jsx("span", { className: "text-micro text-muted", children: credit.detail }))] }), _jsxs("span", { className: "ml-auto flex shrink-0 items-center gap-2", children: [_jsx("span", { className: "text-meta whitespace-nowrap tabular-nums text-ink", children: credit.value }), _jsx(Button, { role: "ghost", size: "sm", icon: Plus, "aria-label": fillCopy(copy.totals.applyCredit, { label: credit.label }), onClick: credit.onApply, children: copy.totals.apply })] })] }, i))) })] }));
|
|
44
|
+
}
|
|
45
|
+
export function Totals({ rows, deductions = [], balance, settled, lead, credits = [], creditsLabel, }) {
|
|
46
|
+
const copy = useUiCopy();
|
|
47
|
+
const leads = lead ??
|
|
48
|
+
(balance !== undefined && deductions.length > 0 && settled === undefined
|
|
49
|
+
? "balance"
|
|
50
|
+
: "total");
|
|
51
|
+
/* With nothing to lead it, the balance cannot lead: the total keeps the title size. */
|
|
52
|
+
const balanceLeads = leads === "balance" && balance !== undefined;
|
|
53
|
+
return (_jsxs("div", { className: "ml-auto flex w-full max-w-(--container-search) flex-col gap-3", children: [_jsxs("dl", { className: "flex flex-col gap-1.5", children: [rows.map((row, i) => row.total === true ? (balanceLeads ? (_jsx(ClosingRow, { row: row }, i)) : (_jsx(LeadRow, { row: row, ...(settled === undefined ? {} : { mark: settled }) }, i))) : (_jsx(PlainRow, { row: row }, i))), deductions.map((item, i) => (_jsx(DeductionRow, { item: item }, i))), balance === undefined ? null : balanceLeads ? (_jsx(LeadRow, { row: balance, ...(settled === undefined ? {} : { mark: settled }) })) : (_jsx(ClosingRow, { row: balance }))] }), credits.length === 0 ? null : (_jsx(Credits, { items: credits, label: creditsLabel ?? copy.totals.credits }))] }));
|
|
4
54
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { Tone } from "../../foundations/types.js";
|
|
2
1
|
import type { ReactNode } from "react";
|
|
3
2
|
export declare const formatCount: (value: number, locale?: string) => string;
|
|
4
3
|
export declare const formatNumber: (value: number, locale?: string) => string;
|
|
@@ -21,42 +20,6 @@ export declare function Numeric({ value, unit, format, }: {
|
|
|
21
20
|
unit?: string;
|
|
22
21
|
format?: "integer" | "decimal" | "distance";
|
|
23
22
|
}): import("react").JSX.Element;
|
|
24
|
-
/**
|
|
25
|
-
* A real bar, never a spinner.
|
|
26
|
-
*
|
|
27
|
-
* Context: a spinner says «wait» and doesn't say how much; a bar says both
|
|
28
|
-
* things.
|
|
29
|
-
*
|
|
30
|
-
* ── Why the fill is FLAT and the gradient is asked for ──────────────
|
|
31
|
-
*
|
|
32
|
-
* The bar always lived with `bar-brand`, the blue→gold gradient. At 3 px and at
|
|
33
|
-
* 62 % that doesn't read as a brand: it reads as a blue bar with a broken tip.
|
|
34
|
-
* A gradient needs length to be a gradient, and a bar inside a list row doesn't
|
|
35
|
-
* have it.
|
|
36
|
-
*
|
|
37
|
-
* Now the fill is a flat tone —and the tone SAYS something: `good` finished,
|
|
38
|
-
* `warn` is running slow, `bad` failed— and the gradient is asked for with
|
|
39
|
-
* `tone="brand"`, which is the long bar of a live import: there it does have room
|
|
40
|
-
* to be read.
|
|
41
|
-
*
|
|
42
|
-
* And it got fatter, from 3 px to 6 px. At 3 px it isn't a bar, it's a line: it
|
|
43
|
-
* can't be told apart from a separator and the fill isn't visible unless it goes
|
|
44
|
-
* past 20 %.
|
|
45
|
-
*/
|
|
46
|
-
export declare function ProgressBar({ value, label, width, tone,
|
|
47
|
-
/** The percentage next to it. It is turned off when the bar lives inside
|
|
48
|
-
* something that already says the figure —a cell with its column— and it
|
|
49
|
-
* would only mean reading it twice. */
|
|
50
|
-
showValue, }: {
|
|
51
|
-
/** 0 to 100. */
|
|
52
|
-
value: number;
|
|
53
|
-
label: string;
|
|
54
|
-
/** The activity bar wants to be longer than a list row; there is no reason for
|
|
55
|
-
* it to be the same number on both sides. */
|
|
56
|
-
width?: string;
|
|
57
|
-
tone?: Tone | "brand";
|
|
58
|
-
showValue?: boolean;
|
|
59
|
-
}): import("react").JSX.Element;
|
|
60
23
|
/**
|
|
61
24
|
* The content of a data cell, with what the system decides and the view does not.
|
|
62
25
|
*
|
|
@@ -47,45 +47,6 @@ export function Numeric({ value, unit, format = "integer", }) {
|
|
|
47
47
|
: formatCount(value);
|
|
48
48
|
return (_jsxs("span", { className: "block text-right text-body tabular-nums whitespace-nowrap text-ink", children: [_jsx("span", { className: "tabular-nums", children: text }), unit === undefined ? null : _jsx("span", { className: "ml-1 text-muted", children: unit })] }));
|
|
49
49
|
}
|
|
50
|
-
/**
|
|
51
|
-
* A real bar, never a spinner.
|
|
52
|
-
*
|
|
53
|
-
* Context: a spinner says «wait» and doesn't say how much; a bar says both
|
|
54
|
-
* things.
|
|
55
|
-
*
|
|
56
|
-
* ── Why the fill is FLAT and the gradient is asked for ──────────────
|
|
57
|
-
*
|
|
58
|
-
* The bar always lived with `bar-brand`, the blue→gold gradient. At 3 px and at
|
|
59
|
-
* 62 % that doesn't read as a brand: it reads as a blue bar with a broken tip.
|
|
60
|
-
* A gradient needs length to be a gradient, and a bar inside a list row doesn't
|
|
61
|
-
* have it.
|
|
62
|
-
*
|
|
63
|
-
* Now the fill is a flat tone —and the tone SAYS something: `good` finished,
|
|
64
|
-
* `warn` is running slow, `bad` failed— and the gradient is asked for with
|
|
65
|
-
* `tone="brand"`, which is the long bar of a live import: there it does have room
|
|
66
|
-
* to be read.
|
|
67
|
-
*
|
|
68
|
-
* And it got fatter, from 3 px to 6 px. At 3 px it isn't a bar, it's a line: it
|
|
69
|
-
* can't be told apart from a separator and the fill isn't visible unless it goes
|
|
70
|
-
* past 20 %.
|
|
71
|
-
*/
|
|
72
|
-
export function ProgressBar({ value, label, width = "w-24", tone = "brand",
|
|
73
|
-
/** The percentage next to it. It is turned off when the bar lives inside
|
|
74
|
-
* something that already says the figure —a cell with its column— and it
|
|
75
|
-
* would only mean reading it twice. */
|
|
76
|
-
showValue = true, }) {
|
|
77
|
-
const clamped = Math.max(0, Math.min(100, Math.round(value)));
|
|
78
|
-
const fill = tone === "brand" ? "bar-brand" : FILL[tone];
|
|
79
|
-
return (_jsxs("span", { className: "flex items-center gap-2", children: [_jsx("span", { className: `h-(--size-track) overflow-hidden rounded-pill bg-ink/10 ${width}`, role: "progressbar", "aria-valuenow": clamped, "aria-valuemin": 0, "aria-valuemax": 100, "aria-label": label, children: _jsx("span", { className: `block h-full rounded-pill transition-[width] duration-(--duration-panel) ease-out ${fill}`, style: { width: `${clamped}%` } }) }), showValue ? (_jsxs("span", { className: "text-meta tabular-nums text-muted", children: [clamped, "%"] })) : null] }));
|
|
80
|
-
}
|
|
81
|
-
const FILL = {
|
|
82
|
-
neutral: "bg-muted",
|
|
83
|
-
info: "bg-info",
|
|
84
|
-
good: "bg-good",
|
|
85
|
-
warn: "bg-warn",
|
|
86
|
-
bad: "bg-bad",
|
|
87
|
-
accent: "bg-accent",
|
|
88
|
-
};
|
|
89
50
|
/**
|
|
90
51
|
* The content of a data cell, with what the system decides and the view does not.
|
|
91
52
|
*
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useUiCopy } from "../../foundations/copy.js";
|
|
2
|
+
import type { LucideIcon } from "lucide-react";
|
|
3
|
+
/** The ways to reach a person. Each one that is present becomes a row of the contact card. */
|
|
4
|
+
export type PersonContact = {
|
|
5
|
+
email?: string;
|
|
6
|
+
/** As people write it — «+591 7 123-4567». The `tel:` link keeps only the digits and `+`. */
|
|
7
|
+
phone?: string;
|
|
8
|
+
/** The WhatsApp number with its country code, written any way; `wa.me` gets the digits. */
|
|
9
|
+
whatsapp?: string;
|
|
10
|
+
};
|
|
11
|
+
/** The three contact links, built from what people type. `wa.me` takes digits only — a `+`,
|
|
12
|
+
* a space or a dash breaks it — and `tel:` takes digits and the leading `+`. */
|
|
13
|
+
export declare function contactRows(contact: PersonContact, copy: ReturnType<typeof useUiCopy>["person"]): {
|
|
14
|
+
key: string;
|
|
15
|
+
icon: LucideIcon;
|
|
16
|
+
label: string;
|
|
17
|
+
value: string;
|
|
18
|
+
href: string;
|
|
19
|
+
}[];
|
|
20
|
+
/**
|
|
21
|
+
* The ways to reach someone, one ROW each — write, call, WhatsApp — on the card of any record
|
|
22
|
+
* that is a person or a company. Each way is a row and not a bare icon: the address and the
|
|
23
|
+
* number are drawn, because often what the person wants is to read the number aloud, not to
|
|
24
|
+
* dial it from the browser. Every row is a real link (`mailto:`, `tel:`, `wa.me`), so
|
|
25
|
+
* middle-click, «copy address» and the phone's own dialer work. Internal: `RelationCard` draws
|
|
26
|
+
* it from its `contact`.
|
|
27
|
+
*/
|
|
28
|
+
export declare function ContactRows({ contact }: {
|
|
29
|
+
contact: PersonContact;
|
|
30
|
+
}): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useUiCopy } from "../../foundations/copy.js";
|
|
4
|
+
import { Mail, MessageCircle, Phone } from "lucide-react";
|
|
5
|
+
/** The three contact links, built from what people type. `wa.me` takes digits only — a `+`,
|
|
6
|
+
* a space or a dash breaks it — and `tel:` takes digits and the leading `+`. */
|
|
7
|
+
export function contactRows(contact, copy) {
|
|
8
|
+
const rows = [];
|
|
9
|
+
if (contact.email !== undefined && contact.email !== "") {
|
|
10
|
+
rows.push({
|
|
11
|
+
key: "email",
|
|
12
|
+
icon: Mail,
|
|
13
|
+
label: copy.email,
|
|
14
|
+
value: contact.email,
|
|
15
|
+
href: `mailto:${contact.email}`,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
if (contact.phone !== undefined && contact.phone !== "") {
|
|
19
|
+
rows.push({
|
|
20
|
+
key: "call",
|
|
21
|
+
icon: Phone,
|
|
22
|
+
label: copy.call,
|
|
23
|
+
value: contact.phone,
|
|
24
|
+
href: `tel:${contact.phone.replace(/[^\d+]/g, "")}`,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
if (contact.whatsapp !== undefined && contact.whatsapp !== "") {
|
|
28
|
+
rows.push({
|
|
29
|
+
key: "whatsapp",
|
|
30
|
+
icon: MessageCircle,
|
|
31
|
+
label: copy.whatsapp,
|
|
32
|
+
value: contact.whatsapp,
|
|
33
|
+
href: `https://wa.me/${contact.whatsapp.replace(/\D/g, "")}`,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return rows;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The ways to reach someone, one ROW each — write, call, WhatsApp — on the card of any record
|
|
40
|
+
* that is a person or a company. Each way is a row and not a bare icon: the address and the
|
|
41
|
+
* number are drawn, because often what the person wants is to read the number aloud, not to
|
|
42
|
+
* dial it from the browser. Every row is a real link (`mailto:`, `tel:`, `wa.me`), so
|
|
43
|
+
* middle-click, «copy address» and the phone's own dialer work. Internal: `RelationCard` draws
|
|
44
|
+
* it from its `contact`.
|
|
45
|
+
*/
|
|
46
|
+
export function ContactRows({ contact }) {
|
|
47
|
+
const rows = contactRows(contact, useUiCopy().person);
|
|
48
|
+
if (rows.length === 0)
|
|
49
|
+
return null;
|
|
50
|
+
return (_jsx("ul", { className: "-mx-1 flex flex-col border-t border-line pt-2", children: rows.map(({ key, icon: Icon, label, value, href }) => (_jsx("li", { children: _jsxs("a", { href: href, "data-contact": key, ...(key === "whatsapp" ? { target: "_blank", rel: "noreferrer" } : {}), className: "flex min-w-0 items-center gap-2.5 rounded-control px-1 py-1.5 no-underline outline-primary outline-offset-2 hover:bg-ink/6 focus-visible:outline-2 forced-colors:outline-[Highlight]", children: [_jsx(Icon, { "aria-hidden": true, className: "size-(--size-icon-md) shrink-0 text-primary", strokeWidth: 2 }), _jsxs("span", { className: "flex min-w-0 flex-col", children: [_jsx("span", { className: "text-meta font-medium text-ink", children: label }), _jsx("span", { className: "truncate text-micro tabular-nums text-muted", children: value })] })] }) }, key))) }));
|
|
51
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
export { Copyable, type CopyableProps } from "./Copyable.js";
|
|
1
|
+
export { Copyable, type CopyableProps, CopyButton, } from "./Copyable.js";
|
|
2
2
|
export { DateValue, formatDateTime, formatRelative, } from "./Fecha.js";
|
|
3
3
|
export { Meter, type MeterLevel, meterLevel, type MeterProps, type MeterThresholds, } from "./Meter.js";
|
|
4
4
|
export { Metric, type MetricProps, Metrics } from "./Metric.js";
|
|
5
|
-
export { Avatar, type AvatarSize,
|
|
5
|
+
export { Avatar, type AvatarSize, type PersonContact, UserChip, type UserChipProps, UserStack, } from "./Person.js";
|
|
6
|
+
export { ProgressBar, type ProgressBarProps, } from "./ProgressBar.js";
|
|
6
7
|
export { RelationCard, type RelationCardProps, type RelationSummary, RelationValue, type RelationValueProps, } from "./Relation.js";
|
|
7
|
-
export { Totals, type TotalsRow } from "./Totals.js";
|
|
8
|
-
export { Cell, formatCount, formatDistance, formatNumber, KeyValue, Numeric,
|
|
8
|
+
export { Totals, type TotalsCredit, type TotalsDeduction, type TotalsFact, type TotalsProps, type TotalsRow, } from "./Totals.js";
|
|
9
|
+
export { Cell, formatCount, formatDistance, formatNumber, KeyValue, Numeric, } from "./Value.js";
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/* Show a number or a datum so that it reads as such and not as text. */
|
|
2
|
-
export { Copyable } from "./Copyable.js";
|
|
2
|
+
export { Copyable, CopyButton, } from "./Copyable.js";
|
|
3
3
|
export { DateValue, formatDateTime, formatRelative, } from "./Fecha.js";
|
|
4
4
|
export { Meter, meterLevel, } from "./Meter.js";
|
|
5
5
|
export { Metric, Metrics } from "./Metric.js";
|
|
6
|
-
export { Avatar,
|
|
6
|
+
export { Avatar, UserChip, UserStack, } from "./Person.js";
|
|
7
|
+
export { ProgressBar, } from "./ProgressBar.js";
|
|
7
8
|
export { RelationCard, RelationValue, } from "./Relation.js";
|
|
8
|
-
export { Totals } from "./Totals.js";
|
|
9
|
-
export { Cell, formatCount, formatDistance, formatNumber, KeyValue, Numeric,
|
|
9
|
+
export { Totals, } from "./Totals.js";
|
|
10
|
+
export { Cell, formatCount, formatDistance, formatNumber, KeyValue, Numeric, } from "./Value.js";
|