@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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Children, Fragment, isValidElement } from "react";
|
|
1
|
+
import { Children, cloneElement, Fragment, isValidElement } from "react";
|
|
2
2
|
/**
|
|
3
3
|
* Marca un componente como ranura y lo devuelve.
|
|
4
4
|
*
|
|
@@ -26,11 +26,16 @@ export function slotOf(node) {
|
|
|
26
26
|
* medio del árbol —lo que escribe cualquiera que ponga dos ranuras bajo un
|
|
27
27
|
* condicional— escondería las ranuras que tiene adentro.
|
|
28
28
|
*/
|
|
29
|
-
export function flatten(children) {
|
|
29
|
+
export function flatten(children, prefix = "") {
|
|
30
30
|
const out = [];
|
|
31
31
|
for (const child of Children.toArray(children)) {
|
|
32
32
|
if (isValidElement(child) && child.type === Fragment) {
|
|
33
|
-
|
|
33
|
+
/* The fragment's children are keyed from zero again («.0», «.1»…): without the
|
|
34
|
+
fragment's own key in front they collide with their new siblings. */
|
|
35
|
+
out.push(...flatten(child.props.children, `${prefix}${String(child.key)}/`));
|
|
36
|
+
}
|
|
37
|
+
else if (prefix !== "" && isValidElement(child)) {
|
|
38
|
+
out.push(cloneElement(child, { key: `${prefix}${String(child.key)}` }));
|
|
34
39
|
}
|
|
35
40
|
else {
|
|
36
41
|
out.push(child);
|
|
@@ -21,3 +21,10 @@ export type Tone = "neutral" | "info" | "good" | "warn" | "bad" | "accent";
|
|
|
21
21
|
* Three and not five: a fourth intermediate step gets picked out of habit and
|
|
22
22
|
* stops meaning a place. */
|
|
23
23
|
export type Size = "sm" | "md" | "lg";
|
|
24
|
+
/**
|
|
25
|
+
* WHERE a record opens, in the words a workspace (the composer) uses: the page (with its crumb
|
|
26
|
+
* back), a modal over the surface that asked, a peek (a drawer, reading without leaving), a
|
|
27
|
+
* side panel, a floating window; web-ui only SAYS which one the person chose — opening is the
|
|
28
|
+
* app's (`api.open(ref, { to })`). See the guide «Cómo abrimos las cosas».
|
|
29
|
+
*/
|
|
30
|
+
export type OpenTarget = "modal" | "peek" | "page" | "side" | "window";
|
|
@@ -12,39 +12,3 @@
|
|
|
12
12
|
* `list/`, even if it is shared across its three files.
|
|
13
13
|
* ==================================================================== */
|
|
14
14
|
export {};
|
|
15
|
-
/* ==================================================================== *
|
|
16
|
-
* THE SYSTEM'S TWO API CONVENTIONS
|
|
17
|
-
*
|
|
18
|
-
* They are here and not in the README because a convention that lives in the
|
|
19
|
-
* documentation breaks in the first component written in a hurry. Here it is
|
|
20
|
-
* read by whoever is going to write the next component.
|
|
21
|
-
*
|
|
22
|
-
* ── 1. How a callback is named ──────────────────────────────────────
|
|
23
|
-
*
|
|
24
|
-
* onXChange the component proposes a NEW value for `x`, and whoever
|
|
25
|
-
* uses it decides whether to accept it. It ALWAYS goes with
|
|
26
|
-
* the `x` prop.
|
|
27
|
-
* → onTermsChange(terms), onSelectedChange(set), onSortChange(sort)
|
|
28
|
-
*
|
|
29
|
-
* onX something happened that is not a value change: an intent.
|
|
30
|
-
* → onClose(), onOpen(), onPromote(), onBrowseAll()
|
|
31
|
-
*
|
|
32
|
-
* The test for knowing which one: **is there a prop with that name next to
|
|
33
|
-
* it?** If `value` exists, it is `onValueChange`. If not, it is an event and
|
|
34
|
-
* goes without a suffix.
|
|
35
|
-
*
|
|
36
|
-
* ── 2. Controlled, always ───────────────────────────────────────────
|
|
37
|
-
*
|
|
38
|
-
* Any state the consumer might want to read, store in the URL or restore on
|
|
39
|
-
* reload is CONTROLLED: `value` + `onValueChange`, with no internal memory.
|
|
40
|
-
* That is what lets the entire workspace live in the hash.
|
|
41
|
-
*
|
|
42
|
-
* The exception, and there is only one: **what dies with the component and
|
|
43
|
-
* nobody cares about afterwards.** An open menu, an expanded filter panel, the
|
|
44
|
-
* half-typed text in the search box. That does keep state inside, because
|
|
45
|
-
* exposing it would force every consumer to declare a `useState` for something
|
|
46
|
-
* they are never going to read.
|
|
47
|
-
*
|
|
48
|
-
* Put another way: if it makes sense to send it to a colleague over chat so
|
|
49
|
-
* they see the same thing, it is controlled.
|
|
50
|
-
* ==================================================================== */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { Block, type BlockProps, type BlockStatus, Rule,
|
|
1
|
+
export { Block, type BlockProps, type BlockStatus, Rule, } from "./Block.js";
|
|
2
2
|
export { FullWidth, SectionGrid } from "./SectionGrid.js";
|
|
3
3
|
export { Line } from "./Line.js";
|
|
4
4
|
export { Step, type StepProps, Steps, type StepsProps, useSteps, } from "./Steps.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* The structure of a view: where each section starts and ends. */
|
|
2
|
-
export { Block, Rule,
|
|
2
|
+
export { Block, Rule, } from "./Block.js";
|
|
3
3
|
export { FullWidth, SectionGrid } from "./SectionGrid.js";
|
|
4
4
|
/* La línea de tres zonas: lo que centra el centro contra la LÍNEA y no contra
|
|
5
5
|
lo que sobra. La usan las dos bandas — la del registro y la de la lista. */
|
|
@@ -31,7 +31,7 @@ export declare function Thumb({ src, alt, icon: Icon, size, }: {
|
|
|
31
31
|
* card. The second line wraps or truncates (`wrap`); the first always truncates, with the full
|
|
32
32
|
* text on hover.
|
|
33
33
|
*/
|
|
34
|
-
export declare function CellStack({ primary, secondary, media, wrap, }: {
|
|
34
|
+
export declare function CellStack({ primary, secondary, media, avatar, wrap, }: {
|
|
35
35
|
/** What the row is — the name. In ink, one line. */
|
|
36
36
|
primary: ReactNode;
|
|
37
37
|
/** What tells it apart — a description, a code, a reference. Small and quiet. */
|
|
@@ -42,6 +42,12 @@ export declare function CellStack({ primary, secondary, media, wrap, }: {
|
|
|
42
42
|
alt?: string;
|
|
43
43
|
icon?: LucideIcon;
|
|
44
44
|
};
|
|
45
|
+
/** A person or a company instead of `media`: its round face — the photo, or the initials in
|
|
46
|
+
* the color it has everywhere. The same contract as a list row's `avatar`. */
|
|
47
|
+
avatar?: {
|
|
48
|
+
name: string;
|
|
49
|
+
src?: string;
|
|
50
|
+
};
|
|
45
51
|
/** Let the second line wrap (a description) instead of truncating (a code). */
|
|
46
52
|
wrap?: boolean;
|
|
47
53
|
}): import("react").JSX.Element;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Avatar } from "../value/Avatar.js";
|
|
2
3
|
/**
|
|
3
4
|
* A thumbnail — the photo of a product, the logo of a customer — or, when there is none, its
|
|
4
5
|
* icon on a quiet tile. One size per context so a column of them lines up.
|
|
@@ -28,6 +29,8 @@ export function Thumb({ src, alt, icon: Icon, size = "md", }) {
|
|
|
28
29
|
* card. The second line wraps or truncates (`wrap`); the first always truncates, with the full
|
|
29
30
|
* text on hover.
|
|
30
31
|
*/
|
|
31
|
-
export function CellStack({ primary, secondary, media, wrap = false, }) {
|
|
32
|
-
return (_jsxs("span", { className: "flex min-w-0 items-center gap-2.5", children: [
|
|
32
|
+
export function CellStack({ primary, secondary, media, avatar, wrap = false, }) {
|
|
33
|
+
return (_jsxs("span", { className: "flex min-w-0 items-center gap-2.5", children: [avatar !== undefined ? (
|
|
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: avatar.name, size: "lg", ...(avatar.src === undefined ? {} : { src: avatar.src }) }) })) : media === undefined ? null : (_jsx(Thumb, { size: "md", ...(media.src === undefined ? {} : { src: media.src }), ...(media.alt === undefined ? {} : { alt: media.alt }), ...(media.icon === undefined ? {} : { icon: media.icon }) })), _jsxs("span", { className: "flex min-w-0 flex-col", children: [_jsx("span", { className: "truncate text-meta font-medium text-ink", ...(typeof primary === "string" ? { title: primary } : {}), children: primary }), secondary === undefined ? null : (_jsx("span", { className: `text-micro text-muted ${wrap ? "line-clamp-2" : "truncate"}`, ...(typeof secondary === "string" ? { title: secondary } : {}), children: secondary }))] })] }));
|
|
33
36
|
}
|
|
@@ -10,7 +10,7 @@ import { Popover } from "../overlay/Popover.js";
|
|
|
10
10
|
import { Check, ChevronDown } from "lucide-react";
|
|
11
11
|
import { ComboBox as AriaComboBox, Input, ListBox, ListBoxItem, Text, useFilter, } from "react-aria-components";
|
|
12
12
|
/**
|
|
13
|
-
* One of
|
|
13
|
+
* One of a closed list, found by typing: a country, a currency, a tax regime, a unit.
|
|
14
14
|
*
|
|
15
15
|
* Context: a `Select` makes you scroll a list you already know the name in; here you type
|
|
16
16
|
* three letters of it. The filter is React Aria's `contains` in the locale of the copy table
|
|
@@ -18,9 +18,13 @@ import { ComboBox as AriaComboBox, Input, ListBox, ListBoxItem, Text, useFilter,
|
|
|
18
18
|
* The value is the option's `value` string — what the backend stores — never React Aria's
|
|
19
19
|
* `Key`.
|
|
20
20
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
21
|
+
* The four ways to choose, and which is which:
|
|
22
|
+
* - `Select` a short closed list (a handful): open it and read it.
|
|
23
|
+
* - `ComboBox` a long closed list already in memory (tens to a few hundred): type three
|
|
24
|
+
* letters; one muted line under the name is enough to tell two apart.
|
|
25
|
+
* - `Picker` a RECORD — a customer, a product — among thousands: rows with face, facts
|
|
26
|
+
* and widgets, «Buscar más…», create, and opening the chosen one.
|
|
27
|
+
* - `Suggest` free text that proposes what already exists without forbidding a new one.
|
|
24
28
|
*
|
|
25
29
|
* When nothing matches, the list simply closes: an empty floating box tells nothing the
|
|
26
30
|
* unchanged field does not, and the system has no "no results" sentence of its own here.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type KanbanStateValue = "normal" | "done" | "blocked";
|
|
2
|
+
/**
|
|
3
|
+
* Odoo's kanban state — the traffic light on a card: in progress, ready for the next stage,
|
|
4
|
+
* blocked.
|
|
5
|
+
*
|
|
6
|
+
* Context: it answers «can this move on?» without opening the record, and the stage alone does
|
|
7
|
+
* not say it — two cards in «propuesta», one waiting on the customer, one ready to send. A dot
|
|
8
|
+
* never goes alone (docs/PRINCIPLES.md §3): its word is the button's name and title, and the
|
|
9
|
+
* list it opens says all three. Without `onChange` it only shows.
|
|
10
|
+
*/
|
|
11
|
+
export declare function KanbanState({ value, onChange, }: {
|
|
12
|
+
value: KanbanStateValue;
|
|
13
|
+
onChange?: (next: KanbanStateValue) => void;
|
|
14
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useUiCopy } from "../../foundations/copy.js";
|
|
4
|
+
import { PopoverTrigger } from "../overlay/PopoverTrigger.js";
|
|
5
|
+
const ORDER = ["normal", "done", "blocked"];
|
|
6
|
+
const FILL = {
|
|
7
|
+
normal: "bg-line-strong",
|
|
8
|
+
done: "bg-good",
|
|
9
|
+
blocked: "bg-bad",
|
|
10
|
+
};
|
|
11
|
+
/** The dot as `PopoverTrigger`'s opener: it hands over `expanded` / `haspopup`, which a bare
|
|
12
|
+
* `<button>` would pass to the DOM as unknown attributes. */
|
|
13
|
+
function DotButton({ expanded, haspopup, ...rest }) {
|
|
14
|
+
return _jsx("button", { type: "button", "aria-expanded": expanded, "aria-haspopup": haspopup, ...rest });
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Odoo's kanban state — the traffic light on a card: in progress, ready for the next stage,
|
|
18
|
+
* blocked.
|
|
19
|
+
*
|
|
20
|
+
* Context: it answers «can this move on?» without opening the record, and the stage alone does
|
|
21
|
+
* not say it — two cards in «propuesta», one waiting on the customer, one ready to send. A dot
|
|
22
|
+
* never goes alone (docs/PRINCIPLES.md §3): its word is the button's name and title, and the
|
|
23
|
+
* list it opens says all three. Without `onChange` it only shows.
|
|
24
|
+
*/
|
|
25
|
+
export function KanbanState({ value, onChange, }) {
|
|
26
|
+
const copy = useUiCopy();
|
|
27
|
+
const words = copy.kanbanState;
|
|
28
|
+
const said = `${words.label}: ${words[value]}`;
|
|
29
|
+
const dot = (state) => (_jsx("span", { "aria-hidden": true, className: `block size-2.5 rounded-pill ${FILL[state]}` }));
|
|
30
|
+
if (onChange === undefined) {
|
|
31
|
+
return (_jsx("span", { role: "img", "aria-label": said, title: said, className: "inline-flex p-1", children: dot(value) }));
|
|
32
|
+
}
|
|
33
|
+
return (_jsx(PopoverTrigger, { label: words.label, placement: "bottom end", trigger: _jsx(DotButton, { "aria-label": said, title: said, className: "relative z-10 inline-flex rounded-pill p-1 transition-colors duration-(--duration-instant) hover:bg-ink/8 focus-visible:outline-2 focus-visible:outline-primary", children: dot(value) }), children: (close) => (_jsx("div", { role: "radiogroup", "aria-label": words.label, className: "flex flex-col p-1", children: ORDER.map((state) => (_jsxs("button", { type: "button", role: "radio", "aria-checked": state === value, onClick: () => {
|
|
34
|
+
onChange(state);
|
|
35
|
+
close();
|
|
36
|
+
}, className: `flex items-center gap-2 rounded-control px-2 py-1.5 text-left text-meta text-ink transition-colors duration-(--duration-instant) hover:bg-ink/5 ${state === value ? "font-medium" : ""}`, children: [dot(state), words[state]] }, state))) })) }));
|
|
37
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type PriorityProps = {
|
|
2
|
+
/** 0 is «sin prioridad». */
|
|
3
|
+
value: number;
|
|
4
|
+
/** How many stars. Odoo's three, by default. */
|
|
5
|
+
max?: number;
|
|
6
|
+
/** Without it the stars only show. Pressing the star already set clears it, the way Odoo
|
|
7
|
+
* does — the only way back to «none» that needs no extra control. */
|
|
8
|
+
onChange?: (next: number) => void;
|
|
9
|
+
/** The group's name, when it is not «Prioridad». */
|
|
10
|
+
label?: string;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Priority as stars: how urgent a lead, a task or a ticket is, read in a list at a glance.
|
|
14
|
+
*
|
|
15
|
+
* Context: a select «baja / media / alta» is read as a word in a column of words; stars are
|
|
16
|
+
* counted without reading. They are a radio group: arrows move, and it is named — «Prioridad:
|
|
17
|
+
* 2 de 3» — so it is not colour alone. In the `warn` tone, not the gold: the gold is the
|
|
18
|
+
* screen's primary action.
|
|
19
|
+
*/
|
|
20
|
+
export declare function Priority({ value, max, onChange, label }: PriorityProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { fillCopy, useUiCopy } from "../../foundations/copy.js";
|
|
4
|
+
import { Star } from "lucide-react";
|
|
5
|
+
import { useRef } from "react";
|
|
6
|
+
/**
|
|
7
|
+
* Priority as stars: how urgent a lead, a task or a ticket is, read in a list at a glance.
|
|
8
|
+
*
|
|
9
|
+
* Context: a select «baja / media / alta» is read as a word in a column of words; stars are
|
|
10
|
+
* counted without reading. They are a radio group: arrows move, and it is named — «Prioridad:
|
|
11
|
+
* 2 de 3» — so it is not colour alone. In the `warn` tone, not the gold: the gold is the
|
|
12
|
+
* screen's primary action.
|
|
13
|
+
*/
|
|
14
|
+
export function Priority({ value, max = 3, onChange, label }) {
|
|
15
|
+
const copy = useUiCopy();
|
|
16
|
+
const name = label ?? copy.priority.label;
|
|
17
|
+
const stars = Array.from({ length: max }, (_, at) => at + 1);
|
|
18
|
+
const buttons = useRef([]);
|
|
19
|
+
const said = value === 0 ? copy.priority.none : fillCopy(copy.priority.level, { value, max });
|
|
20
|
+
const icon = (n) => (_jsx(Star, { "aria-hidden": true, className: `size-(--size-icon-md) ${n <= value ? "fill-warn text-warn" : "text-line-strong"}`, strokeWidth: 1.75 }));
|
|
21
|
+
if (onChange === undefined) {
|
|
22
|
+
return (_jsx("span", { role: "img", "aria-label": `${name}: ${said}`, className: "inline-flex items-center gap-0.5", children: stars.map((n) => (_jsx("span", { children: icon(n) }, n))) }));
|
|
23
|
+
}
|
|
24
|
+
const step = (event, n) => {
|
|
25
|
+
const next = event.key === "ArrowRight" || event.key === "ArrowUp"
|
|
26
|
+
? Math.min(max, n + 1)
|
|
27
|
+
: event.key === "ArrowLeft" || event.key === "ArrowDown"
|
|
28
|
+
? Math.max(1, n - 1)
|
|
29
|
+
: null;
|
|
30
|
+
if (next === null)
|
|
31
|
+
return;
|
|
32
|
+
event.preventDefault();
|
|
33
|
+
onChange(next);
|
|
34
|
+
buttons.current[next - 1]?.focus();
|
|
35
|
+
};
|
|
36
|
+
return (_jsx("span", { role: "radiogroup", "aria-label": name, className: "inline-flex items-center gap-0.5", children: stars.map((n) => (_jsx("button", { ref: (node) => {
|
|
37
|
+
buttons.current[n - 1] = node;
|
|
38
|
+
}, type: "button", role: "radio", "aria-checked": value === n, "aria-label": fillCopy(copy.priority.level, { value: n, max }),
|
|
39
|
+
/* One stop in the tab order — the set star, or the first — and the arrows inside. */
|
|
40
|
+
tabIndex: n === Math.max(value, 1) ? 0 : -1, onClick: () => onChange(n === value ? 0 : n), onKeyDown: (event) => step(event, n), className: "relative z-10 rounded-control p-0.5 transition-colors duration-(--duration-instant) hover:bg-ink/6 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-primary", children: icon(n) }, n))) }));
|
|
41
|
+
}
|
|
@@ -12,5 +12,10 @@ export type SliderProps = {
|
|
|
12
12
|
describedBy?: string;
|
|
13
13
|
/** Forces the mode. By default it inherits the form's: in read mode it draws the figure. */
|
|
14
14
|
mode?: FormMode;
|
|
15
|
+
/** The name when it is NOT inside a `Field` — a toolbar's zoom. Inside a field, the
|
|
16
|
+
* field's label names it. */
|
|
17
|
+
"aria-label"?: string;
|
|
18
|
+
/** The figure beside the track. Off for a control whose number means nothing — a zoom. */
|
|
19
|
+
output?: boolean;
|
|
15
20
|
};
|
|
16
|
-
export declare function Slider({ value, onChange, min, max, step, format, disabled, id: ownId, describedBy: ownDescribedBy, mode, }: SliderProps): import("react").JSX.Element;
|
|
21
|
+
export declare function Slider({ value, onChange, min, max, step, format, disabled, id: ownId, describedBy: ownDescribedBy, mode, "aria-label": ariaLabel, output, }: SliderProps): import("react").JSX.Element;
|
|
@@ -33,7 +33,7 @@ const thumb = tv({
|
|
|
33
33
|
},
|
|
34
34
|
},
|
|
35
35
|
});
|
|
36
|
-
export function Slider({ value, onChange, min, max, step = 1, format, disabled = false, id: ownId, describedBy: ownDescribedBy, mode, }) {
|
|
36
|
+
export function Slider({ value, onChange, min, max, step = 1, format, disabled = false, id: ownId, describedBy: ownDescribedBy, mode, "aria-label": ariaLabel, output = true, }) {
|
|
37
37
|
const copy = useUiCopy();
|
|
38
38
|
const { id, labelledBy, describedBy } = useFieldIds({
|
|
39
39
|
id: ownId,
|
|
@@ -42,5 +42,5 @@ export function Slider({ value, onChange, min, max, step = 1, format, disabled =
|
|
|
42
42
|
if (useFormMode(mode) === "read") {
|
|
43
43
|
return _jsx(ReadValue, { value: new Intl.NumberFormat(copy.locale, format).format(value) });
|
|
44
44
|
}
|
|
45
|
-
return (_jsx(AriaLocale, { children: _jsxs(AriaSlider, { id: id, "aria-labelledby": labelledBy, "aria-describedby": describedBy, value: value, onChange: (next) => onChange?.(next), minValue: min, maxValue: max, step: step, formatOptions: format, isDisabled: disabled, className: "flex h-(--size-control-md) w-full min-w-0 items-center gap-3 disabled:opacity-45", children: [_jsxs(SliderTrack, { className: "relative flex h-(--size-control-md) min-w-0 flex-1 cursor-pointer items-center disabled:cursor-not-allowed", children: [_jsx("span", { className: "relative h-1 w-full rounded-pill bg-line-strong forced-colors:bg-[ButtonBorder]", children: _jsx(SliderFill, { className: "rounded-pill bg-primary disabled:bg-muted forced-colors:bg-[Highlight]" }) }), _jsx(SliderThumb, { "aria-labelledby": labelledBy, "aria-describedby": describedBy, className: thumb })] }), _jsx(SliderOutput, { className: "w-14 shrink-0 text-right text-meta text-ink tabular-nums" })] }) }));
|
|
45
|
+
return (_jsx(AriaLocale, { children: _jsxs(AriaSlider, { id: id, "aria-labelledby": labelledBy, "aria-label": ariaLabel, "aria-describedby": describedBy, value: value, onChange: (next) => onChange?.(next), minValue: min, maxValue: max, step: step, formatOptions: format, isDisabled: disabled, className: "flex h-(--size-control-md) w-full min-w-0 items-center gap-3 disabled:opacity-45", children: [_jsxs(SliderTrack, { className: "relative flex h-(--size-control-md) min-w-0 flex-1 cursor-pointer items-center disabled:cursor-not-allowed", children: [_jsx("span", { className: "relative h-1 w-full rounded-pill bg-line-strong forced-colors:bg-[ButtonBorder]", children: _jsx(SliderFill, { className: "rounded-pill bg-primary disabled:bg-muted forced-colors:bg-[Highlight]" }) }), _jsx(SliderThumb, { "aria-labelledby": labelledBy, "aria-label": ariaLabel, "aria-describedby": describedBy, className: thumb })] }), output ? (_jsx(SliderOutput, { className: "w-14 shrink-0 text-right text-meta text-ink tabular-nums" })) : null] }) }));
|
|
46
46
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { ComboBox, type ComboBoxProps } from "./ComboBox.js";
|
|
2
|
+
export { KanbanState, type KanbanStateValue, } from "./KanbanState.js";
|
|
2
3
|
export { type ChoiceOption, type SegmentOption, } from "./options.js";
|
|
4
|
+
export { Priority, type PriorityProps } from "./Priority.js";
|
|
3
5
|
export { RadioGroup, type RadioGroupProps, } from "./RadioGroup.js";
|
|
4
6
|
export { SearchField, type SearchFieldProps, } from "./SearchField.js";
|
|
5
7
|
export { SegmentedControl, type SegmentedControlProps, } from "./SegmentedControl.js";
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
(RadioGroup, SegmentedControl), prender o apagar (Switch), un valor en un rango (Slider)
|
|
3
3
|
y buscar un texto suelto (SearchField). El valor es siempre un dato plano. */
|
|
4
4
|
export { ComboBox } from "./ComboBox.js";
|
|
5
|
+
export { KanbanState, } from "./KanbanState.js";
|
|
5
6
|
export {} from "./options.js";
|
|
7
|
+
export { Priority } from "./Priority.js";
|
|
6
8
|
export { RadioGroup, } from "./RadioGroup.js";
|
|
7
9
|
export { SearchField, } from "./SearchField.js";
|
|
8
10
|
export { SegmentedControl, } from "./SegmentedControl.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ReactNode } from "react";
|
|
1
|
+
import { type PointerEvent, type ReactNode } from "react";
|
|
2
2
|
/** Where this dialog came from. It is drawn in the header, small. */
|
|
3
3
|
export interface DialogContext {
|
|
4
4
|
/** The category, in small caps: «factura», «cliente». It answers *what kind of
|
|
@@ -52,13 +52,19 @@ export interface DialogProps {
|
|
|
52
52
|
* It is the tooltip and the accessible name, and it is not drawn. */
|
|
53
53
|
expandLabel?: string;
|
|
54
54
|
footer?: ReactNode;
|
|
55
|
+
/** The window can be dragged by its header, to see what is behind it — the form a «search
|
|
56
|
+
* more» was opened from. Off on a phone, where the window is a bottom sheet. */
|
|
57
|
+
movable?: boolean;
|
|
55
58
|
children: ReactNode;
|
|
56
59
|
}
|
|
57
60
|
/** The inside of the window: React Aria's `Dialog` with the header, body and footer.
|
|
58
61
|
*
|
|
59
62
|
* Internal — exported only so a test can read its markup, which the modal (a portal)
|
|
60
63
|
* does not produce on the server. */
|
|
61
|
-
export declare function DialogSurface({ title, subtitle, context, onClose, onExpand, expandLabel, footer, children, }: Omit<DialogProps, "open" | "width"
|
|
64
|
+
export declare function DialogSurface({ title, subtitle, context, onClose, onExpand, expandLabel, footer, children, onDragStart, }: Omit<DialogProps, "open" | "width" | "movable"> & {
|
|
65
|
+
/** Set by a movable `Dialog`: the header is the handle. */
|
|
66
|
+
onDragStart?: (event: PointerEvent<HTMLElement>) => void;
|
|
67
|
+
}): import("react").JSX.Element;
|
|
62
68
|
/**
|
|
63
69
|
* What has to be answered before moving on: a short form that blocks the page and keeps
|
|
64
70
|
* saying what it is about.
|
|
@@ -68,7 +74,7 @@ export declare function DialogSurface({ title, subtitle, context, onClose, onExp
|
|
|
68
74
|
* stays mounted for the exit transition after `open` turns `false`, so the window fades
|
|
69
75
|
* out instead of vanishing.
|
|
70
76
|
*/
|
|
71
|
-
export declare function Dialog({ open, width, onClose, ...surface }: DialogProps): import("react").JSX.Element;
|
|
77
|
+
export declare function Dialog({ open, width, onClose, movable, ...surface }: DialogProps): import("react").JSX.Element;
|
|
72
78
|
export interface WizardStep {
|
|
73
79
|
id: string;
|
|
74
80
|
label: string;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useUiCopy } from "../../foundations/copy.js";
|
|
4
|
+
import { HANDHELD_WIDTH } from "../../foundations/layout.js";
|
|
4
5
|
import { cn, tv } from "../../foundations/variants.js";
|
|
5
6
|
import { Button, IconButton } from "../button/Button.js";
|
|
6
7
|
import { Maximize2, X } from "lucide-react";
|
|
7
|
-
import { useId } from "react";
|
|
8
|
+
import { useEffect, useId, useState } from "react";
|
|
8
9
|
import { Dialog as AriaDialog, Heading, Modal, ModalOverlay } from "react-aria-components";
|
|
9
10
|
/** The scrim. The page stays visible under it — the dialog is ABOUT something on it.
|
|
10
11
|
* On a phone it drops the padding and pushes the window to the bottom edge. */
|
|
@@ -39,11 +40,11 @@ export const dialogStyles = { overlay, modal };
|
|
|
39
40
|
*
|
|
40
41
|
* Internal — exported only so a test can read its markup, which the modal (a portal)
|
|
41
42
|
* does not produce on the server. */
|
|
42
|
-
export function DialogSurface({ title, subtitle, context, onClose, onExpand, expandLabel, footer, children, }) {
|
|
43
|
+
export function DialogSurface({ title, subtitle, context, onClose, onExpand, expandLabel, footer, children, onDragStart, }) {
|
|
43
44
|
const copy = useUiCopy();
|
|
44
45
|
const subtitleId = useId();
|
|
45
46
|
const expand = expandLabel ?? copy.dialog.expand;
|
|
46
|
-
return (_jsxs(AriaDialog, { "aria-describedby": subtitle === undefined ? undefined : subtitleId, className: "flex max-h-[inherit] min-h-0 flex-1 flex-col outline-0", children: [_jsxs("header", { className: "flex shrink-0 items-start gap-3 border-b border-line px-4 py-3", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [context === undefined ? null : (
|
|
47
|
+
return (_jsxs(AriaDialog, { "aria-describedby": subtitle === undefined ? undefined : subtitleId, className: "flex max-h-[inherit] min-h-0 flex-1 flex-col outline-0", children: [_jsxs("header", { onPointerDown: onDragStart, className: cn("flex shrink-0 items-start gap-3 border-b border-line px-4 py-3", onDragStart !== undefined && "cursor-grab select-none active:cursor-grabbing"), children: [_jsxs("div", { className: "min-w-0 flex-1", children: [context === undefined ? null : (
|
|
47
48
|
/* The context, on top and small. It is what keeps the first field
|
|
48
49
|
from being «about what?». Same voice as a field label, not a
|
|
49
50
|
console kicker: mono uppercase next to a form title was a
|
|
@@ -59,11 +60,46 @@ export function DialogSurface({ title, subtitle, context, onClose, onExpand, exp
|
|
|
59
60
|
* stays mounted for the exit transition after `open` turns `false`, so the window fades
|
|
60
61
|
* out instead of vanishing.
|
|
61
62
|
*/
|
|
62
|
-
export function Dialog({ open, width = "md", onClose, ...surface }) {
|
|
63
|
+
export function Dialog({ open, width = "md", onClose, movable = false, ...surface }) {
|
|
64
|
+
const [offset, setOffset] = useState({ x: 0, y: 0 });
|
|
65
|
+
/* Every opening starts centred: where it was dragged last time is not where it belongs. */
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
if (open)
|
|
68
|
+
setOffset({ x: 0, y: 0 });
|
|
69
|
+
}, [open]);
|
|
70
|
+
/* `transform` and not `translate`: the window enters and leaves on `translate`, which is
|
|
71
|
+
transitioned — a drag on it would trail the pointer by the panel's duration. */
|
|
72
|
+
const drag = (event) => {
|
|
73
|
+
if (event.button !== 0 || event.pointerType === "touch")
|
|
74
|
+
return;
|
|
75
|
+
if (event.target.closest("button, a, input") !== null)
|
|
76
|
+
return;
|
|
77
|
+
/* Below it the window is a bottom sheet, and a sheet is not dragged around. */
|
|
78
|
+
if (window.innerWidth < HANDHELD_WIDTH)
|
|
79
|
+
return;
|
|
80
|
+
const from = { x: event.clientX - offset.x, y: event.clientY - offset.y };
|
|
81
|
+
/* Never further than half the screen from the centre: the header — the only handle — has
|
|
82
|
+
to stay where a pointer can reach it. */
|
|
83
|
+
const halfW = window.innerWidth / 2;
|
|
84
|
+
const halfH = window.innerHeight / 2;
|
|
85
|
+
const clamp = (value, half) => Math.max(-half, Math.min(half, value));
|
|
86
|
+
const move = (next) => setOffset({
|
|
87
|
+
x: clamp(next.clientX - from.x, halfW),
|
|
88
|
+
y: clamp(next.clientY - from.y, halfH),
|
|
89
|
+
});
|
|
90
|
+
const stop = () => {
|
|
91
|
+
window.removeEventListener("pointermove", move);
|
|
92
|
+
window.removeEventListener("pointerup", stop);
|
|
93
|
+
};
|
|
94
|
+
window.addEventListener("pointermove", move);
|
|
95
|
+
window.addEventListener("pointerup", stop);
|
|
96
|
+
};
|
|
63
97
|
return (_jsx(ModalOverlay, { isOpen: open, onOpenChange: (next) => {
|
|
64
98
|
if (!next)
|
|
65
99
|
onClose();
|
|
66
|
-
}, isDismissable: true, className: overlay, children: _jsx(Modal, { className: (state) => modal({ ...state, width }),
|
|
100
|
+
}, isDismissable: true, className: overlay, children: _jsx(Modal, { className: (state) => modal({ ...state, width }), ...(movable
|
|
101
|
+
? { style: { transform: `translate(${offset.x}px, ${offset.y}px)` } }
|
|
102
|
+
: {}), children: _jsx(DialogSurface, { onClose: onClose, ...surface, ...(movable ? { onDragStart: drag } : {}) }) }) }));
|
|
67
103
|
}
|
|
68
104
|
/** A dialog with steps, for what CANNOT be answered in one go.
|
|
69
105
|
*
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { Callout, type CalloutProps } from "./Callout.js";
|
|
2
2
|
export { Flag, type FlagProps, type FlagTone } from "./Flag.js";
|
|
3
3
|
export { Empty, Failed, ICONO_ROTO, Missing, Skeleton, } from "./Notice.js";
|
|
4
|
-
export {
|
|
4
|
+
export { ToastProvider, type ToastSpec, useToast, useToastIfAny, } from "./Toast.js";
|
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
export { Callout } from "./Callout.js";
|
|
4
4
|
export { Flag } from "./Flag.js";
|
|
5
5
|
export { Empty, Failed, ICONO_ROTO, Missing, Skeleton, } from "./Notice.js";
|
|
6
|
-
export {
|
|
6
|
+
export { ToastProvider, useToast, useToastIfAny, } from "./Toast.js";
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { Popover } from "./Popover.js";
|
|
4
4
|
import { isValidElement } from "react";
|
|
5
|
+
import { setInteractionModality } from "react-aria";
|
|
5
6
|
import { Focusable, Link, PreviewTrigger } from "react-aria-components";
|
|
6
7
|
/**
|
|
7
8
|
* A glance at a record without leaving the screen: the customer of an invoice, the product
|
|
@@ -53,8 +54,16 @@ const PREVIEW_DELAY = 600;
|
|
|
53
54
|
const PREVIEW_CLOSE_DELAY = 200;
|
|
54
55
|
export function Preview({ trigger, label, children, placement = "bottom start", delay = PREVIEW_DELAY, closeDelay = PREVIEW_CLOSE_DELAY, disabled = false, }) {
|
|
55
56
|
const readsContext = isValidElement(trigger) && trigger.type === Link;
|
|
56
|
-
return (
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
return (
|
|
58
|
+
/* React Aria opens a preview on hover only once it KNOWS the pointer is in use — and it
|
|
59
|
+
learns that from a press, not from a move. On a page just loaded, before any click, the
|
|
60
|
+
pointer rested on a name and nothing opened. Entering the trigger with a mouse says it,
|
|
61
|
+
in the capture phase so it is said before React Aria's own hover handler asks. */
|
|
62
|
+
_jsx("span", { className: "contents", onPointerOverCapture: (event) => {
|
|
63
|
+
if (event.pointerType === "mouse")
|
|
64
|
+
setInteractionModality("pointer");
|
|
65
|
+
}, children: _jsxs(PreviewTrigger, { delay: delay, closeDelay: closeDelay, isDisabled: disabled, children: [readsContext ? (trigger) : (
|
|
66
|
+
/* React Aria types the child as an intrinsic element; a component that forwards
|
|
67
|
+
`ref` and spreads native props is the same thing to it at runtime. */
|
|
68
|
+
_jsx(Focusable, { children: trigger })), _jsx(Popover, { placement: placement, "aria-label": label, children: _jsx("div", { className: "max-h-[inherit] max-w-sm overflow-auto p-3", children: children }) })] }) }));
|
|
60
69
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A text with the part that matched the search marked — internal to the lists that filter
|
|
3
|
+
* as you type (`Picker`, `TagsField`). A `<mark>`, so it is also said as marked text, and
|
|
4
|
+
* bold besides the tint: in forced colors the tint is gone and the weight is what remains.
|
|
5
|
+
*/
|
|
6
|
+
export declare function MatchedText({ text, query, locale, }: {
|
|
7
|
+
text: string;
|
|
8
|
+
query: string;
|
|
9
|
+
locale?: string;
|
|
10
|
+
}): string | (string | import("react").JSX.Element)[];
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { matchSpans } from "../../foundations/match.js";
|
|
3
|
+
/**
|
|
4
|
+
* A text with the part that matched the search marked — internal to the lists that filter
|
|
5
|
+
* as you type (`Picker`, `TagsField`). A `<mark>`, so it is also said as marked text, and
|
|
6
|
+
* bold besides the tint: in forced colors the tint is gone and the weight is what remains.
|
|
7
|
+
*/
|
|
8
|
+
export function MatchedText({ text, query, locale, }) {
|
|
9
|
+
if (query.trim() === "")
|
|
10
|
+
return text;
|
|
11
|
+
return matchSpans(text, query, locale).map((span, index) => span.hit ? (_jsx("mark", { className: "rounded-mark bg-accent/25 font-semibold text-current", children: span.text }, index)) : (span.text));
|
|
12
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { OpenTarget } from "../../foundations/types.js";
|
|
1
2
|
import { type FormMode } from "../field/mode.js";
|
|
2
3
|
import type { LucideIcon } from "lucide-react";
|
|
3
4
|
import { type ReactNode } from "react";
|
|
@@ -21,6 +22,13 @@ export interface PickerItem {
|
|
|
21
22
|
/** The header this item is listed under. Consecutive items with the same section share
|
|
22
23
|
* one header; an item without it is listed loose, outside any section. */
|
|
23
24
|
section?: string;
|
|
25
|
+
/** A person or a company: its face before the name — the photo, or its initials when
|
|
26
|
+
* `true`. Who is being chosen is recognised before it is read, in the list and in the field. */
|
|
27
|
+
avatar?: true | string;
|
|
28
|
+
/** What is read at a glance on the right, in tabular figures: the balance owed, the stock,
|
|
29
|
+
* a state badge, a priority — a widget, not text. Where `meta` describes, this COMPARES:
|
|
30
|
+
* lined up in one column, eight customers' debts are read top to bottom. */
|
|
31
|
+
aside?: ReactNode;
|
|
24
32
|
}
|
|
25
33
|
/**
|
|
26
34
|
* What the list shows and how many are left out of it. Capped at 8 with the footer count,
|
|
@@ -40,12 +48,24 @@ export type PickerProps = {
|
|
|
40
48
|
value?: string;
|
|
41
49
|
onChange?: (id: string) => void;
|
|
42
50
|
placeholder?: string;
|
|
43
|
-
/**
|
|
44
|
-
*
|
|
45
|
-
|
|
51
|
+
/** «Buscar más…»: the last option of the list, always there when this is — the record
|
|
52
|
+
* may not be among the eight, or two of them may need their columns to tell apart. It
|
|
53
|
+
* gets what was typed, so the list that chooses (`List onChoose`, in a modal of the
|
|
54
|
+
* workspace) opens on the same search. */
|
|
55
|
+
onBrowseAll?: (query: string) => void;
|
|
56
|
+
/** «Crear «…»»: a record made from the typed text alone (Odoo's `name_create`); it returns
|
|
57
|
+
* the new id — or a promise of it — and the field chooses it. Offered while something is
|
|
58
|
+
* typed that no option is called exactly. The consumer appends the new item to `items`. */
|
|
59
|
+
onCreate?: (name: string) => string | Promise<string>;
|
|
60
|
+
/** «Crear y editar…»: the new record needs more than a name — open its form (in a workspace,
|
|
61
|
+
* a modal) with the typed text as the name. Choosing it there is the consumer's. */
|
|
62
|
+
onCreateAndEdit?: (name: string) => void;
|
|
46
63
|
/** Open the chosen record. Drawn as a trailing affordance, the way a many2one
|
|
47
64
|
* offers "open" next to "pick". Absent when there is nothing chosen. */
|
|
48
|
-
onOpen?: (id: string) => void;
|
|
65
|
+
onOpen?: (id: string, to: OpenTarget) => void;
|
|
66
|
+
/** Where the chosen record can open, the first being the button's (a modal, by default: the
|
|
67
|
+
* form stays behind); the rest go in «Abrir en…». `onOpen` is told which was picked. */
|
|
68
|
+
openTargets?: readonly OpenTarget[];
|
|
49
69
|
emptyText?: string;
|
|
50
70
|
/** Placeholder of the field while it is being typed into. It is NOT a second
|
|
51
71
|
* box — the field itself is the search. */
|
|
@@ -56,7 +76,7 @@ export type PickerProps = {
|
|
|
56
76
|
loadingText?: string;
|
|
57
77
|
/** The list grows from the backend: called when its end scrolls into view. The consumer
|
|
58
78
|
* appends the next page to `items`; with nothing more to fetch it does nothing. Turns off
|
|
59
|
-
* the cap of 8
|
|
79
|
+
* the cap of 8 and the footer count. */
|
|
60
80
|
onLoadMore?: () => void;
|
|
61
81
|
/** A page asked for by `onLoadMore` is on its way: a quiet row at the end says so. Unlike
|
|
62
82
|
* `loading`, the rows already there stay. */
|
|
@@ -72,15 +92,17 @@ export type PickerProps = {
|
|
|
72
92
|
};
|
|
73
93
|
export declare function Picker({ mode, ...props }: PickerProps): import("react").JSX.Element;
|
|
74
94
|
/**
|
|
75
|
-
* The list of a `Picker`: its rows, the section headers, the «
|
|
95
|
+
* The list of a `Picker`: its rows, the section headers, the «Buscar más…» row and the loading
|
|
76
96
|
* row at the end. Internal — not exported from the family — and apart from the combobox so it
|
|
77
97
|
* can be rendered on the server without a popover, which is how the tests see the rows.
|
|
78
98
|
*/
|
|
79
|
-
export declare function PickerOptions({ items, marks, hints, remaining, emptyText, onLoadMore, loadingMore, "aria-label": ariaLabel, }: {
|
|
99
|
+
export declare function PickerOptions({ items, marks, hints, remaining, query, emptyText, onLoadMore, loadingMore, "aria-label": ariaLabel, }: {
|
|
80
100
|
items: readonly PickerItem[];
|
|
81
101
|
marks: boolean;
|
|
82
102
|
hints: boolean;
|
|
83
103
|
remaining: number;
|
|
104
|
+
/** What was typed: it is marked in every row, so each row says why it is there. */
|
|
105
|
+
query?: string;
|
|
84
106
|
emptyText: string;
|
|
85
107
|
onLoadMore?: () => void;
|
|
86
108
|
loadingMore?: boolean;
|