@sincpro/web-ui 0.2.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 +3 -3
- package/dist/components/activity/ActivityBadge.d.ts +16 -0
- package/dist/components/activity/ActivityBadge.js +29 -0
- package/dist/components/activity/index.d.ts +1 -0
- package/dist/components/activity/index.js +1 -0
- 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.js +5 -1
- 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 +3 -0
- package/dist/components/index.js +3 -0
- package/dist/components/lines/LineItems.d.ts +5 -2
- package/dist/components/lines/LineItems.js +3 -3
- package/dist/components/listing/BoardColumns.d.ts +39 -0
- package/dist/components/listing/BoardColumns.js +138 -0
- package/dist/components/listing/ListingBar.d.ts +5 -0
- package/dist/components/listing/ListingBar.js +5 -2
- 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 +21 -2
- package/dist/components/listing/Row.js +29 -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 +7 -1
- package/dist/components/record/RecordHeader.js +30 -18
- package/dist/components/record/RecordRibbon.d.ts +13 -0
- package/dist/components/record/RecordRibbon.js +22 -0
- 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 +106 -14
- package/dist/foundations/copy.es.js +83 -9
- package/dist/foundations/copy.js +83 -9
- 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.js +8 -4
- 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/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/dialog/Dialog.d.ts +9 -3
- package/dist/primitives/dialog/Dialog.js +41 -5
- package/dist/primitives/dialog/index.d.ts +0 -1
- package/dist/primitives/dialog/index.js +0 -2
- package/dist/primitives/notice/index.d.ts +1 -1
- package/dist/primitives/notice/index.js +1 -1
- package/dist/primitives/overlay/Preview.js +13 -4
- package/dist/primitives/picker/MatchedText.d.ts +10 -0
- package/dist/primitives/picker/MatchedText.js +12 -0
- package/dist/primitives/picker/Picker.d.ts +29 -7
- package/dist/primitives/picker/Picker.js +88 -23
- package/dist/primitives/table/EditableTable.d.ts +6 -1
- package/dist/primitives/table/EditableTable.js +70 -9
- package/dist/primitives/table/ReportTable.js +2 -1
- package/dist/primitives/table/RowActions.js +7 -2
- package/dist/primitives/table/Table.d.ts +5 -0
- package/dist/primitives/table/Table.js +17 -8
- package/dist/primitives/tag/Tags.d.ts +2 -0
- package/dist/primitives/tag/Tags.js +2 -1
- package/dist/primitives/tag/TagsField.d.ts +10 -0
- package/dist/primitives/tag/TagsField.js +34 -9
- package/dist/primitives/tag/choices.d.ts +2 -0
- package/dist/primitives/value/Avatar.d.ts +16 -0
- package/dist/primitives/value/Avatar.js +91 -0
- package/dist/primitives/value/Person.d.ts +6 -51
- package/dist/primitives/value/Person.js +9 -148
- package/dist/primitives/value/Relation.d.ts +21 -9
- package/dist/primitives/value/Relation.js +35 -24
- package/dist/primitives/value/contact.d.ts +30 -0
- package/dist/primitives/value/contact.js +51 -0
- package/dist/primitives/value/index.d.ts +1 -1
- package/dist/primitives/value/index.js +1 -1
- package/dist/tokens.css +136 -1
- package/dist/views/list/List.d.ts +25 -2
- package/dist/views/list/List.js +48 -8
- package/dist/views/list/ListView.d.ts +1 -1
- package/dist/views/list/ListView.js +39 -8
- package/dist/views/list/columns.js +8 -1
- package/dist/views/list/presentations.d.ts +5 -2
- package/dist/views/list/presentations.js +32 -2
- package/dist/views/list/types.d.ts +44 -0
- package/dist/views/list/useNarrowList.d.ts +10 -0
- package/dist/views/list/useNarrowList.js +20 -6
- package/dist/views/record/Record.d.ts +14 -0
- package/dist/views/record/Record.js +22 -7
- package/package.json +11 -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/primitives/dialog/Drill.d.ts +0 -90
- package/dist/primitives/dialog/Drill.js +0 -172
|
@@ -8,9 +8,11 @@ import { useFieldIds } from "../field/context.js";
|
|
|
8
8
|
import { ReadValue } from "../field/Field.js";
|
|
9
9
|
import { useFormMode } from "../field/mode.js";
|
|
10
10
|
import { Popover } from "../overlay/Popover.js";
|
|
11
|
+
import { MatchedText } from "../picker/MatchedText.js";
|
|
11
12
|
import { tagChoices, withoutTag, withTag, } from "./choices.js";
|
|
12
13
|
import { Tags } from "./Tags.js";
|
|
13
|
-
import {
|
|
14
|
+
import { Avatar } from "../value/Person.js";
|
|
15
|
+
import { Plus, Search } from "lucide-react";
|
|
14
16
|
import { useContext, useEffect, useMemo, useRef, useState, } from "react";
|
|
15
17
|
import { ComboBox as AriaComboBox, ComboBoxStateContext, Group, Input, ListBox, ListBoxItem, useFilter, useLocale, } from "react-aria-components";
|
|
16
18
|
/* ==================================================================== *
|
|
@@ -43,6 +45,15 @@ import { ComboBox as AriaComboBox, ComboBoxStateContext, Group, Input, ListBox,
|
|
|
43
45
|
* ==================================================================== */
|
|
44
46
|
/** The key of the «create» row. Not an id: it cannot collide with one. */
|
|
45
47
|
const CREATE = "\u0000create";
|
|
48
|
+
/** The key of the «Buscar más…» row. */
|
|
49
|
+
const BROWSE = "\u0000browse";
|
|
50
|
+
/** What a chip needs to open its record: the press, and the place it opens named as its title. */
|
|
51
|
+
function useChipOpening(onOpen, to = "peek") {
|
|
52
|
+
const copy = useUiCopy();
|
|
53
|
+
return (id) => onOpen === undefined
|
|
54
|
+
? {}
|
|
55
|
+
: { onSelect: () => onOpen(id, to), title: copy.picker.openIn[to] };
|
|
56
|
+
}
|
|
46
57
|
/** The chosen tags as chips, in the order of `value`. An id missing from `options` is still
|
|
47
58
|
* drawn (as itself): hiding it would hide data the record has. */
|
|
48
59
|
function chosenOptions(options, value) {
|
|
@@ -56,6 +67,7 @@ function chosenOptions(options, value) {
|
|
|
56
67
|
* chips, and its hint and error describe the input. In read mode it is `Tags`.
|
|
57
68
|
*/
|
|
58
69
|
export function TagsField({ mode, ...props }) {
|
|
70
|
+
const opening = useChipOpening(props.onOpen, props.openTarget);
|
|
59
71
|
if (useFormMode(mode) === "read") {
|
|
60
72
|
const chosen = chosenOptions(props.options, props.value);
|
|
61
73
|
if (chosen.length === 0)
|
|
@@ -64,19 +76,22 @@ export function TagsField({ mode, ...props }) {
|
|
|
64
76
|
id: option.value,
|
|
65
77
|
label: option.label,
|
|
66
78
|
...(option.tone === undefined ? {} : { tone: option.tone }),
|
|
79
|
+
...(option.avatar === undefined ? {} : { avatar: option.avatar }),
|
|
80
|
+
...opening(option.value),
|
|
67
81
|
})) }) }));
|
|
68
82
|
}
|
|
69
83
|
/* The filter reads the locale from React Aria's context, so the box that provides it has
|
|
70
84
|
to be ABOVE the component that asks for it. */
|
|
71
85
|
return (_jsx(AriaLocale, { children: _jsx(EditableTagsField, { ...props }) }));
|
|
72
86
|
}
|
|
73
|
-
function EditableTagsField({ value, onChange, options, onCreate, placeholder, disabled = false, id: ownId, describedBy: ownDescribedBy, invalid: ownInvalid, "aria-label": ariaLabel, }) {
|
|
87
|
+
function EditableTagsField({ value, onChange, options, onCreate, onBrowseAll, onOpen, openTarget, placeholder, disabled = false, id: ownId, describedBy: ownDescribedBy, invalid: ownInvalid, "aria-label": ariaLabel, }) {
|
|
74
88
|
const { id, labelledBy, describedBy, invalid } = useFieldIds({
|
|
75
89
|
id: ownId,
|
|
76
90
|
describedBy: ownDescribedBy,
|
|
77
91
|
invalid: ownInvalid,
|
|
78
92
|
});
|
|
79
93
|
const copy = useUiCopy();
|
|
94
|
+
const opening = useChipOpening(onOpen, openTarget);
|
|
80
95
|
const { contains } = useFilter({ sensitivity: "base" });
|
|
81
96
|
const { locale } = useLocale();
|
|
82
97
|
const collator = useMemo(() => new Intl.Collator(locale, { sensitivity: "base", usage: "search" }), [locale]);
|
|
@@ -89,17 +104,25 @@ function EditableTagsField({ value, onChange, options, onCreate, placeholder, di
|
|
|
89
104
|
const latest = useRef(value);
|
|
90
105
|
latest.current = value;
|
|
91
106
|
const { available, create } = tagChoices(options, value, text, { contains, same: (a, b) => collator.compare(a.trim(), b) === 0 }, onCreate !== undefined && !disabled);
|
|
92
|
-
const listed =
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
{ value: CREATE, label: fillCopy(copy.inlineEdit.createTag, { name: create }) },
|
|
97
|
-
|
|
107
|
+
const listed = [
|
|
108
|
+
...available,
|
|
109
|
+
...(create === null
|
|
110
|
+
? []
|
|
111
|
+
: [{ value: CREATE, label: fillCopy(copy.inlineEdit.createTag, { name: create }) }]),
|
|
112
|
+
...(onBrowseAll === undefined || disabled
|
|
113
|
+
? []
|
|
114
|
+
: [{ value: BROWSE, label: copy.picker.browseAll }]),
|
|
115
|
+
];
|
|
98
116
|
const commit = (key) => {
|
|
99
117
|
if (key === null)
|
|
100
118
|
return;
|
|
101
119
|
const chosen = String(key);
|
|
120
|
+
const typed = text;
|
|
102
121
|
setText("");
|
|
122
|
+
if (chosen === BROWSE) {
|
|
123
|
+
onBrowseAll?.(typed);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
103
126
|
if (chosen !== CREATE) {
|
|
104
127
|
onChange?.(withTag(latest.current, chosen));
|
|
105
128
|
return;
|
|
@@ -120,6 +143,8 @@ function EditableTagsField({ value, onChange, options, onCreate, placeholder, di
|
|
|
120
143
|
id: option.value,
|
|
121
144
|
label: option.label,
|
|
122
145
|
...(option.tone === undefined ? {} : { tone: option.tone }),
|
|
146
|
+
...(option.avatar === undefined ? {} : { avatar: option.avatar }),
|
|
147
|
+
...opening(option.value),
|
|
123
148
|
...(disabled ? {} : { onRemove: () => remove(option.value) }),
|
|
124
149
|
}));
|
|
125
150
|
return (
|
|
@@ -149,7 +174,7 @@ function EditableTagsField({ value, onChange, options, onCreate, placeholder, di
|
|
|
149
174
|
onChange?.(withoutTag(value, last));
|
|
150
175
|
}, empty: text === "" }), _jsx(Popover, { triggerRef: boxRef, ...(width === null
|
|
151
176
|
? {}
|
|
152
|
-
: { style: { "--trigger-width": `${width}px` } }), placement: "bottom start", className: "w-(--trigger-width)", children: _jsx(ListBox, { items: listed, className: "max-h-[inherit] overflow-auto p-1 outline-0", children: (item) => (_jsx(ListBoxItem, { id: item.value, textValue: item.label, className: (state) => cn(listOptionStyles(state), item.value === CREATE ? "text-primary" : ""), children: item.value === CREATE ? (_jsxs(_Fragment, { children: [_jsx(Plus, { "aria-hidden": true, className: "size-(--size-icon-sm) shrink-0", strokeWidth: 2 }), _jsx("span", { className: "truncate font-medium", children: item.label })] })) : (_jsx("span", { className: "truncate", children: item.label })) })) }) })] })] }));
|
|
177
|
+
: { style: { "--trigger-width": `${width}px` } }), placement: "bottom start", className: "w-(--trigger-width)", children: _jsx(ListBox, { items: listed, dependencies: [text], className: "max-h-[inherit] overflow-auto p-1 outline-0", children: (item) => (_jsx(ListBoxItem, { id: item.value, textValue: item.label, className: (state) => cn(listOptionStyles(state), item.value === CREATE || item.value === BROWSE ? "text-primary" : ""), children: item.value === CREATE ? (_jsxs(_Fragment, { children: [_jsx(Plus, { "aria-hidden": true, className: "size-(--size-icon-sm) shrink-0", strokeWidth: 2 }), _jsx("span", { className: "truncate font-medium", children: item.label })] })) : item.value === BROWSE ? (_jsxs(_Fragment, { children: [_jsx(Search, { "aria-hidden": true, className: "size-(--size-icon-sm) shrink-0", strokeWidth: 2 }), _jsx("span", { className: "truncate font-medium", children: item.label })] })) : (_jsxs(_Fragment, { children: [item.avatar === undefined ? null : (_jsx(Avatar, { name: item.label, size: "sm", ...(typeof item.avatar === "string" ? { src: item.avatar } : {}) })), _jsx("span", { className: "truncate", children: _jsx(MatchedText, { text: item.label, query: text, locale: copy.locale }) })] })) })) }) })] })] }));
|
|
153
178
|
}
|
|
154
179
|
/** The input after the chips. It reads the combobox's state to know whether the keyboard
|
|
155
180
|
* marked an option — Enter with nothing marked takes the first match. */
|
|
@@ -11,6 +11,8 @@ export interface TagOption {
|
|
|
11
11
|
label: string;
|
|
12
12
|
/** Neutral by default: «mayorista» classifies, it does not warn. */
|
|
13
13
|
tone?: Tone;
|
|
14
|
+
/** A person: its face on the chip and in the list — `true` for initials, a URL for a photo. */
|
|
15
|
+
avatar?: true | string;
|
|
14
16
|
}
|
|
15
17
|
/** How the typed text is compared — the locale's, handed in by the component. */
|
|
16
18
|
export type TagMatch = {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const SIZE: {
|
|
2
|
+
readonly sm: "size-5 text-micro";
|
|
3
|
+
readonly md: "size-6 text-micro";
|
|
4
|
+
readonly lg: "size-8 text-meta";
|
|
5
|
+
readonly xl: "size-12 text-body";
|
|
6
|
+
};
|
|
7
|
+
export declare const OVERLAP: Record<AvatarSize, string>;
|
|
8
|
+
export type AvatarSize = keyof typeof SIZE;
|
|
9
|
+
export declare function Avatar({ name, size,
|
|
10
|
+
/** The photo, if there is one. Without it the initials go, which is the normal
|
|
11
|
+
* case here. */
|
|
12
|
+
src, }: {
|
|
13
|
+
name: string;
|
|
14
|
+
size?: AvatarSize;
|
|
15
|
+
src?: string;
|
|
16
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useSurface } from "../../foundations/surface.js";
|
|
4
|
+
/* ==================================================================== *
|
|
5
|
+
* WHO — the person as a datum, not as a string of text.
|
|
6
|
+
*
|
|
7
|
+
* «andres.g@sincpro.com.bo» in a cell is a correct and unreadable datum: twelve
|
|
8
|
+
* rows of emails get read letter by letter. An initial in a circle is
|
|
9
|
+
* recognized without reading, which is what is needed when the question is «who
|
|
10
|
+
* does this belong to?» and not «what is their email?».
|
|
11
|
+
*
|
|
12
|
+
* ── The color comes out of the name and nobody picks it ──────────────
|
|
13
|
+
*
|
|
14
|
+
* A hash of the name against the tone palette. It is deterministic —the same
|
|
15
|
+
* person always has the same color, on every screen— and that is why the color
|
|
16
|
+
* gets to mean something. If the consumer picked it, the same person would be
|
|
17
|
+
* green in one list and blue in another, and recognition without reading is lost
|
|
18
|
+
* entirely.
|
|
19
|
+
*
|
|
20
|
+
* The color here is NOT a state: it is identity. That is why it uses the tone
|
|
21
|
+
* palette but never `bad` nor `warn` — a person is not «in error».
|
|
22
|
+
* ==================================================================== */
|
|
23
|
+
const PALETTE = [
|
|
24
|
+
"bg-info/15 text-info",
|
|
25
|
+
"bg-good/15 text-good",
|
|
26
|
+
"bg-accent/25 text-ink",
|
|
27
|
+
"bg-primary/12 text-primary",
|
|
28
|
+
"bg-ink/10 text-ink",
|
|
29
|
+
];
|
|
30
|
+
/** The initials: two, and from the first two WORDS. «Ana María Gutiérrez» gives
|
|
31
|
+
* `AG` and not `AM` — the surname distinguishes more than the middle name. */
|
|
32
|
+
function initials(name) {
|
|
33
|
+
const parts = name
|
|
34
|
+
.trim()
|
|
35
|
+
.split(/\s+/)
|
|
36
|
+
.filter((p) => p.length > 0);
|
|
37
|
+
const first = parts[0]?.[0] ?? "?";
|
|
38
|
+
const last = parts.length > 1 ? parts[parts.length - 1]?.[0] : undefined;
|
|
39
|
+
return `${first}${last ?? ""}`.toUpperCase();
|
|
40
|
+
}
|
|
41
|
+
/* The same five identities on the dark chrome. The canvas palette there composed its tints
|
|
42
|
+
over the navy and left ink letters on a dark disc — 1.61:1, unreadable (measured on the
|
|
43
|
+
application bar). Same index, so a person keeps their color across the two surfaces. */
|
|
44
|
+
const CHROME_PALETTE = [
|
|
45
|
+
"bg-chrome-link/20 text-chrome-link",
|
|
46
|
+
"bg-chrome-good/20 text-chrome-good",
|
|
47
|
+
"bg-chrome-accent/12 text-chrome-accent",
|
|
48
|
+
"bg-chrome-link/14 text-chrome-ink",
|
|
49
|
+
"bg-chrome-ink/14 text-chrome-ink",
|
|
50
|
+
];
|
|
51
|
+
function toneOf(name, surface = "canvas") {
|
|
52
|
+
let h = 0;
|
|
53
|
+
for (const ch of name)
|
|
54
|
+
h = (h * 31 + ch.codePointAt(0)) % 100000;
|
|
55
|
+
const palette = surface === "chrome" ? CHROME_PALETTE : PALETTE;
|
|
56
|
+
return palette[h % palette.length];
|
|
57
|
+
}
|
|
58
|
+
/* The text doesn't go below `micro`: two initials at 10 px can't be read, and an
|
|
59
|
+
unreadable initial is a colored circle — which is exactly what we don't want.
|
|
60
|
+
What changes between sizes is the circle. */
|
|
61
|
+
export const SIZE = {
|
|
62
|
+
sm: "size-5 text-micro",
|
|
63
|
+
md: "size-6 text-micro",
|
|
64
|
+
lg: "size-8 text-meta",
|
|
65
|
+
/* The head of a contact card: the face is what the card is about. */
|
|
66
|
+
xl: "size-12 text-body",
|
|
67
|
+
};
|
|
68
|
+
/* Two initials, so the overlap stays short of the second letter. The ring is
|
|
69
|
+
1px: a 2px ring of the surface color reads as a gap between people, which
|
|
70
|
+
is the thing a stack is not. */
|
|
71
|
+
export const OVERLAP = {
|
|
72
|
+
sm: "-ml-1.5",
|
|
73
|
+
md: "-ml-2",
|
|
74
|
+
lg: "-ml-2.5",
|
|
75
|
+
xl: "-ml-3.5",
|
|
76
|
+
};
|
|
77
|
+
export function Avatar({ name, size = "md",
|
|
78
|
+
/** The photo, if there is one. Without it the initials go, which is the normal
|
|
79
|
+
* case here. */
|
|
80
|
+
src, }) {
|
|
81
|
+
const surface = useSurface();
|
|
82
|
+
if (src !== undefined) {
|
|
83
|
+
return (
|
|
84
|
+
/* The photo lies over the initials, drawn invisible: an image has no text baseline, so
|
|
85
|
+
a chip with a photo sat on the image's bottom edge in a row aligned by baseline (a
|
|
86
|
+
`DataField`), higher than the label next to it. The hidden initials give the photo
|
|
87
|
+
the same baseline the initials disc has. */
|
|
88
|
+
_jsxs("span", { title: name, className: `relative inline-flex shrink-0 items-center justify-center overflow-hidden rounded-pill ${SIZE[size]}`, children: [_jsx("span", { "aria-hidden": true, className: "invisible", children: initials(name) }), _jsx("img", { src: src, alt: name, className: "absolute inset-0 size-full object-cover" })] }));
|
|
89
|
+
}
|
|
90
|
+
return (_jsx("span", { title: name, "aria-hidden": true, className: `inline-flex shrink-0 items-center justify-center rounded-pill font-medium select-none ${SIZE[size]} ${toneOf(name, surface)}`, children: initials(name) }));
|
|
91
|
+
}
|
|
@@ -1,52 +1,8 @@
|
|
|
1
|
+
import { type AvatarSize } from "./Avatar.js";
|
|
2
|
+
import type { PersonContact } from "./contact.js";
|
|
1
3
|
import type { ReactNode } from "react";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
readonly md: "size-6 text-micro";
|
|
5
|
-
readonly lg: "size-8 text-meta";
|
|
6
|
-
readonly xl: "size-12 text-body";
|
|
7
|
-
};
|
|
8
|
-
export type AvatarSize = keyof typeof SIZE;
|
|
9
|
-
export declare function Avatar({ name, size,
|
|
10
|
-
/** The photo, if there is one. Without it the initials go, which is the normal
|
|
11
|
-
* case here. */
|
|
12
|
-
src, }: {
|
|
13
|
-
name: string;
|
|
14
|
-
size?: AvatarSize;
|
|
15
|
-
src?: string;
|
|
16
|
-
}): import("react").JSX.Element;
|
|
17
|
-
/** The ways to reach a person. Each one that is present becomes a row of the contact card. */
|
|
18
|
-
export type PersonContact = {
|
|
19
|
-
email?: string;
|
|
20
|
-
/** As people write it — «+591 7 123-4567». The `tel:` link keeps only the digits and `+`. */
|
|
21
|
-
phone?: string;
|
|
22
|
-
/** The WhatsApp number with its country code, written any way; `wa.me` gets the digits. */
|
|
23
|
-
whatsapp?: string;
|
|
24
|
-
};
|
|
25
|
-
export type ContactCardProps = {
|
|
26
|
-
name: string;
|
|
27
|
-
/** The photo. Without it, the initials — in the same color the person has everywhere. */
|
|
28
|
-
src?: string;
|
|
29
|
-
/** What the person is here: «Vendedora · Sucursal La Paz». */
|
|
30
|
-
role?: string;
|
|
31
|
-
contact?: PersonContact;
|
|
32
|
-
/** Go to the person's own record — «Ver ficha». Without it the card only reaches them. */
|
|
33
|
-
onOpen?: () => void;
|
|
34
|
-
};
|
|
35
|
-
/**
|
|
36
|
-
* Who a person is and how to reach them: the face, the name, the role, and one row per way
|
|
37
|
-
* of contact — write, call, WhatsApp — then their record.
|
|
38
|
-
*
|
|
39
|
-
* Context: in an ERP the question after «who is the salesperson of this invoice?» is almost
|
|
40
|
-
* always «how do I reach her?». Copying an email out of another screen to paste it into the
|
|
41
|
-
* mail client is the detour this card removes. Each way of contact is a ROW and not a bare
|
|
42
|
-
* icon: the address and the number are drawn, because often what the person wants is to read
|
|
43
|
-
* the number aloud, not to dial it from the browser. Every row is a real link (`mailto:`,
|
|
44
|
-
* `tel:`, `wa.me`), so middle-click, «copy address» and the phone's own dialer work.
|
|
45
|
-
*
|
|
46
|
-
* It is what opens from a `UserChip` with `contact`, and it is exported on its own for the
|
|
47
|
-
* side panel of a record, where the same person is shown without a chip to open.
|
|
48
|
-
*/
|
|
49
|
-
export declare function ContactCard({ name, src, role, contact, onOpen }: ContactCardProps): import("react").JSX.Element;
|
|
4
|
+
export { Avatar, type AvatarSize } from "./Avatar.js";
|
|
5
|
+
export type { PersonContact } from "./contact.js";
|
|
50
6
|
export type UserChipProps = {
|
|
51
7
|
name: string;
|
|
52
8
|
size?: AvatarSize;
|
|
@@ -57,7 +13,7 @@ export type UserChipProps = {
|
|
|
57
13
|
role?: string;
|
|
58
14
|
/** The ways to reach them. Present = the chip opens a contact card. */
|
|
59
15
|
contact?: PersonContact;
|
|
60
|
-
/** Go to the person's record. Present = the chip opens
|
|
16
|
+
/** Go to the person's record. Present = the chip opens its card with «Abrir». */
|
|
61
17
|
onOpen?: () => void;
|
|
62
18
|
};
|
|
63
19
|
/**
|
|
@@ -65,7 +21,7 @@ export type UserChipProps = {
|
|
|
65
21
|
* a fact of a record.
|
|
66
22
|
*
|
|
67
23
|
* Context: a chip is a button ONLY when it has somewhere to go. With `contact` or `onOpen` it
|
|
68
|
-
* opens the `
|
|
24
|
+
* opens the person's `RelationCard` (press, Enter or Space — React Aria's `DialogTrigger` through
|
|
69
25
|
* `PopoverTrigger`: focus goes into the card, Escape and a click outside close it, and focus
|
|
70
26
|
* comes back to the chip). Without them it stays text: a button that does nothing is a lie a
|
|
71
27
|
* keyboard user pays for with one more Tab stop on every row.
|
|
@@ -87,4 +43,3 @@ export declare function UserStack({ names, max, size, }: {
|
|
|
87
43
|
max?: number;
|
|
88
44
|
size?: AvatarSize;
|
|
89
45
|
}): import("react").JSX.Element;
|
|
90
|
-
export {};
|
|
@@ -1,154 +1,15 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx,
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useUiCopy } from "../../foundations/copy.js";
|
|
4
|
-
import { useSurface } from "../../foundations/surface.js";
|
|
5
|
-
import { Button } from "../button/Button.js";
|
|
6
4
|
import { PopoverTrigger } from "../overlay/PopoverTrigger.js";
|
|
7
|
-
import {
|
|
5
|
+
import { Avatar, OVERLAP, SIZE, } from "./Avatar.js";
|
|
6
|
+
import { RelationCard } from "./Relation.js";
|
|
8
7
|
/* ==================================================================== *
|
|
9
|
-
* WHO —
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* rows of emails get read letter by letter. An initial in a circle is
|
|
13
|
-
* recognized without reading, which is what is needed when the question is «who
|
|
14
|
-
* does this belong to?» and not «what is their email?».
|
|
15
|
-
*
|
|
16
|
-
* ── The color comes out of the name and nobody picks it ──────────────
|
|
17
|
-
*
|
|
18
|
-
* A hash of the name against the tone palette. It is deterministic —the same
|
|
19
|
-
* person always has the same color, on every screen— and that is why the color
|
|
20
|
-
* gets to mean something. If the consumer picked it, the same person would be
|
|
21
|
-
* green in one list and blue in another, and recognition without reading is lost
|
|
22
|
-
* entirely.
|
|
23
|
-
*
|
|
24
|
-
* The color here is NOT a state: it is identity. That is why it uses the tone
|
|
25
|
-
* palette but never `bad` nor `warn` — a person is not «in error».
|
|
8
|
+
* WHO — a person as a chip and as a stack. The face itself is `Avatar`; what a person's
|
|
9
|
+
* card says (the face, the role, the ways to reach them, «Abrir») is the `RelationCard` of
|
|
10
|
+
* that person: a person IS a related record, and one card answers for all of them.
|
|
26
11
|
* ==================================================================== */
|
|
27
|
-
|
|
28
|
-
"bg-info/15 text-info",
|
|
29
|
-
"bg-good/15 text-good",
|
|
30
|
-
"bg-accent/25 text-ink",
|
|
31
|
-
"bg-primary/12 text-primary",
|
|
32
|
-
"bg-ink/10 text-ink",
|
|
33
|
-
];
|
|
34
|
-
/** The initials: two, and from the first two WORDS. «Ana María Gutiérrez» gives
|
|
35
|
-
* `AG` and not `AM` — the surname distinguishes more than the middle name. */
|
|
36
|
-
function initials(name) {
|
|
37
|
-
const parts = name
|
|
38
|
-
.trim()
|
|
39
|
-
.split(/\s+/)
|
|
40
|
-
.filter((p) => p.length > 0);
|
|
41
|
-
const first = parts[0]?.[0] ?? "?";
|
|
42
|
-
const last = parts.length > 1 ? parts[parts.length - 1]?.[0] : undefined;
|
|
43
|
-
return `${first}${last ?? ""}`.toUpperCase();
|
|
44
|
-
}
|
|
45
|
-
/* The same five identities on the dark chrome. The canvas palette there composed its tints
|
|
46
|
-
over the navy and left ink letters on a dark disc — 1.61:1, unreadable (measured on the
|
|
47
|
-
application bar). Same index, so a person keeps their color across the two surfaces. */
|
|
48
|
-
const CHROME_PALETTE = [
|
|
49
|
-
"bg-chrome-link/20 text-chrome-link",
|
|
50
|
-
"bg-chrome-good/20 text-chrome-good",
|
|
51
|
-
"bg-chrome-accent/12 text-chrome-accent",
|
|
52
|
-
"bg-chrome-link/14 text-chrome-ink",
|
|
53
|
-
"bg-chrome-ink/14 text-chrome-ink",
|
|
54
|
-
];
|
|
55
|
-
function toneOf(name, surface = "canvas") {
|
|
56
|
-
let h = 0;
|
|
57
|
-
for (const ch of name)
|
|
58
|
-
h = (h * 31 + ch.codePointAt(0)) % 100000;
|
|
59
|
-
const palette = surface === "chrome" ? CHROME_PALETTE : PALETTE;
|
|
60
|
-
return palette[h % palette.length];
|
|
61
|
-
}
|
|
62
|
-
/* The text doesn't go below `micro`: two initials at 10 px can't be read, and an
|
|
63
|
-
unreadable initial is a colored circle — which is exactly what we don't want.
|
|
64
|
-
What changes between sizes is the circle. */
|
|
65
|
-
const SIZE = {
|
|
66
|
-
sm: "size-5 text-micro",
|
|
67
|
-
md: "size-6 text-micro",
|
|
68
|
-
lg: "size-8 text-meta",
|
|
69
|
-
/* The head of a contact card: the face is what the card is about. */
|
|
70
|
-
xl: "size-12 text-body",
|
|
71
|
-
};
|
|
72
|
-
/* Two initials, so the overlap stays short of the second letter. The ring is
|
|
73
|
-
1px: a 2px ring of the surface color reads as a gap between people, which
|
|
74
|
-
is the thing a stack is not. */
|
|
75
|
-
const OVERLAP = {
|
|
76
|
-
sm: "-ml-1.5",
|
|
77
|
-
md: "-ml-2",
|
|
78
|
-
lg: "-ml-2.5",
|
|
79
|
-
xl: "-ml-3.5",
|
|
80
|
-
};
|
|
81
|
-
export function Avatar({ name, size = "md",
|
|
82
|
-
/** The photo, if there is one. Without it the initials go, which is the normal
|
|
83
|
-
* case here. */
|
|
84
|
-
src, }) {
|
|
85
|
-
const surface = useSurface();
|
|
86
|
-
if (src !== undefined) {
|
|
87
|
-
return (
|
|
88
|
-
/* The photo lies over the initials, drawn invisible: an image has no text baseline, so
|
|
89
|
-
a chip with a photo sat on the image's bottom edge in a row aligned by baseline (a
|
|
90
|
-
`DataField`), higher than the label next to it. The hidden initials give the photo
|
|
91
|
-
the same baseline the initials disc has. */
|
|
92
|
-
_jsxs("span", { title: name, className: `relative inline-flex shrink-0 items-center justify-center overflow-hidden rounded-pill ${SIZE[size]}`, children: [_jsx("span", { "aria-hidden": true, className: "invisible", children: initials(name) }), _jsx("img", { src: src, alt: name, className: "absolute inset-0 size-full object-cover" })] }));
|
|
93
|
-
}
|
|
94
|
-
return (_jsx("span", { title: name, "aria-hidden": true, className: `inline-flex shrink-0 items-center justify-center rounded-pill font-medium select-none ${SIZE[size]} ${toneOf(name, surface)}`, children: initials(name) }));
|
|
95
|
-
}
|
|
96
|
-
/** The three contact links, built from what people type. `wa.me` takes digits only — a `+`,
|
|
97
|
-
* a space or a dash breaks it — and `tel:` takes digits and the leading `+`. */
|
|
98
|
-
function contactRows(contact, copy) {
|
|
99
|
-
const rows = [];
|
|
100
|
-
if (contact.email !== undefined && contact.email !== "") {
|
|
101
|
-
rows.push({
|
|
102
|
-
key: "email",
|
|
103
|
-
icon: Mail,
|
|
104
|
-
label: copy.email,
|
|
105
|
-
value: contact.email,
|
|
106
|
-
href: `mailto:${contact.email}`,
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
if (contact.phone !== undefined && contact.phone !== "") {
|
|
110
|
-
rows.push({
|
|
111
|
-
key: "call",
|
|
112
|
-
icon: Phone,
|
|
113
|
-
label: copy.call,
|
|
114
|
-
value: contact.phone,
|
|
115
|
-
href: `tel:${contact.phone.replace(/[^\d+]/g, "")}`,
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
if (contact.whatsapp !== undefined && contact.whatsapp !== "") {
|
|
119
|
-
rows.push({
|
|
120
|
-
key: "whatsapp",
|
|
121
|
-
icon: MessageCircle,
|
|
122
|
-
label: copy.whatsapp,
|
|
123
|
-
value: contact.whatsapp,
|
|
124
|
-
href: `https://wa.me/${contact.whatsapp.replace(/\D/g, "")}`,
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
return rows;
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Who a person is and how to reach them: the face, the name, the role, and one row per way
|
|
131
|
-
* of contact — write, call, WhatsApp — then their record.
|
|
132
|
-
*
|
|
133
|
-
* Context: in an ERP the question after «who is the salesperson of this invoice?» is almost
|
|
134
|
-
* always «how do I reach her?». Copying an email out of another screen to paste it into the
|
|
135
|
-
* mail client is the detour this card removes. Each way of contact is a ROW and not a bare
|
|
136
|
-
* icon: the address and the number are drawn, because often what the person wants is to read
|
|
137
|
-
* the number aloud, not to dial it from the browser. Every row is a real link (`mailto:`,
|
|
138
|
-
* `tel:`, `wa.me`), so middle-click, «copy address» and the phone's own dialer work.
|
|
139
|
-
*
|
|
140
|
-
* It is what opens from a `UserChip` with `contact`, and it is exported on its own for the
|
|
141
|
-
* side panel of a record, where the same person is shown without a chip to open.
|
|
142
|
-
*/
|
|
143
|
-
export function ContactCard({ name, src, role, contact = {}, onOpen }) {
|
|
144
|
-
const copy = useUiCopy().person;
|
|
145
|
-
const rows = contactRows(contact, copy);
|
|
146
|
-
return (
|
|
147
|
-
/* React events bubble through the popover's portal to the React parents of the chip —
|
|
148
|
-
a table row that opens on click among them. A click inside the card is not a click
|
|
149
|
-
on the row. */
|
|
150
|
-
_jsxs("div", { className: "flex w-64 max-w-full flex-col gap-3", onClick: (e) => e.stopPropagation(), children: [_jsxs("div", { className: "flex min-w-0 items-center gap-3", children: [_jsx("span", { "aria-hidden": true, className: "flex shrink-0", children: _jsx(Avatar, { name: name, size: "xl", ...(src === undefined ? {} : { src }) }) }), _jsxs("div", { className: "flex min-w-0 flex-col", children: [_jsx("span", { className: "truncate text-lead font-semibold text-ink", title: name, children: name }), role === undefined ? null : (_jsx("span", { className: "line-clamp-2 text-meta text-muted", title: role, children: role }))] })] }), rows.length === 0 ? null : (_jsx("ul", { className: "-mx-1 flex flex-col", 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))) })), onOpen === undefined ? null : (_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.open }) }))] }));
|
|
151
|
-
}
|
|
12
|
+
export { Avatar } from "./Avatar.js";
|
|
152
13
|
/** The chip as the opener of its card. A native button that takes what `PopoverTrigger`
|
|
153
14
|
* hands its trigger — `ref`, the press handlers, `expanded` and `haspopup` — the same
|
|
154
15
|
* contract the system's buttons keep. */
|
|
@@ -164,7 +25,7 @@ function ChipButton({ expanded, haspopup, children, ref, ...native }) {
|
|
|
164
25
|
* a fact of a record.
|
|
165
26
|
*
|
|
166
27
|
* Context: a chip is a button ONLY when it has somewhere to go. With `contact` or `onOpen` it
|
|
167
|
-
* opens the `
|
|
28
|
+
* opens the person's `RelationCard` (press, Enter or Space — React Aria's `DialogTrigger` through
|
|
168
29
|
* `PopoverTrigger`: focus goes into the card, Escape and a click outside close it, and focus
|
|
169
30
|
* comes back to the chip). Without them it stays text: a button that does nothing is a lie a
|
|
170
31
|
* keyboard user pays for with one more Tab stop on every row.
|
|
@@ -180,7 +41,7 @@ export function UserChip({ name, size = "md", src, detail, role, contact, onOpen
|
|
|
180
41
|
if (!reachable) {
|
|
181
42
|
return _jsx("span", { className: "inline-flex min-w-0 items-center gap-2", children: face });
|
|
182
43
|
}
|
|
183
|
-
return (_jsx(PopoverTrigger, { label: name, trigger: _jsx(ChipButton, { children: face }), children: (close) => (_jsx(
|
|
44
|
+
return (_jsx(PopoverTrigger, { label: name, trigger: _jsx(ChipButton, { children: face }), children: (close) => (_jsx(RelationCard, { name: name, media: { person: true, ...(src === undefined ? {} : { src }) }, ...(role === undefined ? {} : { secondary: role }), ...(contact === undefined ? {} : { contact }), ...(onOpen === undefined
|
|
184
45
|
? {}
|
|
185
46
|
: {
|
|
186
47
|
onOpen: () => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { Tone } from "../../foundations/types.js";
|
|
1
|
+
import type { OpenTarget, Tone } from "../../foundations/types.js";
|
|
2
2
|
import { type FormMode } from "../field/mode.js";
|
|
3
|
+
import { type PersonContact } from "./contact.js";
|
|
3
4
|
import type { LucideIcon } from "lucide-react";
|
|
4
5
|
import { type ReactNode } from "react";
|
|
5
6
|
/** What the floating summary of the related record says. */
|
|
@@ -18,16 +19,22 @@ export type RelationSummary = {
|
|
|
18
19
|
value: ReactNode;
|
|
19
20
|
}[];
|
|
20
21
|
};
|
|
22
|
+
/** The picture of the related record: a photo or an icon on a square tile — or, for a person
|
|
23
|
+
* (`person`), the round face: the photo, or the initials in the color that person has
|
|
24
|
+
* everywhere. */
|
|
21
25
|
type RelationMedia = {
|
|
22
26
|
src?: string;
|
|
23
27
|
alt?: string;
|
|
24
28
|
icon?: LucideIcon;
|
|
29
|
+
person?: true;
|
|
25
30
|
};
|
|
26
31
|
export type RelationCardProps = {
|
|
27
32
|
name: string;
|
|
28
33
|
secondary?: ReactNode;
|
|
29
34
|
media?: RelationMedia;
|
|
30
35
|
summary?: RelationSummary;
|
|
36
|
+
/** How to reach it — a person, a company: one row per way (write, call, WhatsApp). */
|
|
37
|
+
contact?: PersonContact;
|
|
31
38
|
/** «Abrir»: go to the related record. */
|
|
32
39
|
onOpen?: () => void;
|
|
33
40
|
/** The record's address, when it has one: «Abrir» becomes a real link. */
|
|
@@ -39,9 +46,10 @@ export type RelationCardProps = {
|
|
|
39
46
|
* Context: it answers the question a person has about the customer of an invoice without
|
|
40
47
|
* leaving the invoice — «does it owe us?», «what is its NIT?» — and, when the answer is
|
|
41
48
|
* «I need more», one press takes them to the record. It is what floats from a
|
|
42
|
-
* `RelationValue
|
|
49
|
+
* `RelationValue` and what opens from a `UserChip` (a person is a related record too: round
|
|
50
|
+
* face, role, the ways to reach them), and it is exported on its own for a side panel.
|
|
43
51
|
*/
|
|
44
|
-
export declare function RelationCard({ name, secondary, media, summary, onOpen, href, }: RelationCardProps): import("react").JSX.Element;
|
|
52
|
+
export declare function RelationCard({ name, secondary, media, summary, contact, onOpen, href, }: RelationCardProps): import("react").JSX.Element;
|
|
45
53
|
export type RelationValueProps = {
|
|
46
54
|
/** The related record's name. Empty, `null` or absent = no record: said, in muted. */
|
|
47
55
|
name?: string | null;
|
|
@@ -51,14 +59,18 @@ export type RelationValueProps = {
|
|
|
51
59
|
media?: RelationMedia;
|
|
52
60
|
/** Go to the related record. The name becomes a link. */
|
|
53
61
|
onOpen?: () => void;
|
|
62
|
+
/** Go to it in the place the person chose (`OpenTarget`): the name opens in the first of
|
|
63
|
+
* `openTargets` — a modal by default, the form stays behind — and «Abrir en…» beside it offers
|
|
64
|
+
* the rest. Takes over from `onOpen`. */
|
|
65
|
+
onOpenIn?: (to: OpenTarget) => void;
|
|
66
|
+
openTargets?: readonly OpenTarget[];
|
|
54
67
|
/** The related record's address: the name becomes a real `<a>` (middle-click, copy). */
|
|
55
68
|
href?: string;
|
|
56
|
-
/**
|
|
69
|
+
/** The facts of the related record. Present = its `RelationCard` floats from the name
|
|
70
|
+
* (hover, focus, long press); from an info button when the name is not a link. */
|
|
57
71
|
summary?: RelationSummary;
|
|
58
|
-
/**
|
|
59
|
-
*
|
|
60
|
-
* content instead — the product of a line, a consumer's own card. Needs the name to be a
|
|
61
|
-
* link (`href` or `onOpen`); without one the content opens from the info button. */
|
|
72
|
+
/** What floats instead of the `RelationCard` — the product of a line, a consumer's own
|
|
73
|
+
* card. `true` floats the card even without `summary`; `false` floats nothing. */
|
|
62
74
|
preview?: boolean | ReactNode;
|
|
63
75
|
/** The control that EDITS this relation — a `Picker` — drawn instead in edit mode. */
|
|
64
76
|
editor?: ReactNode;
|
|
@@ -70,5 +82,5 @@ export type RelationValueProps = {
|
|
|
70
82
|
* picture, and its summary one press away. Works in a table cell, a list row, a `Record.Facts`
|
|
71
83
|
* value and a form field (read mode; pass the `Picker` as `editor` for edit mode).
|
|
72
84
|
*/
|
|
73
|
-
export declare function RelationValue({ name, secondary, media, onOpen, href, summary, preview, editor, mode, }: RelationValueProps): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | import("react").JSX.Element | null | undefined;
|
|
85
|
+
export declare function RelationValue({ name, secondary, media, onOpen: onOpenOwn, onOpenIn, openTargets, href, summary, preview, editor, mode, }: RelationValueProps): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | import("react").JSX.Element | null | undefined;
|
|
74
86
|
export {};
|