@sincpro/web-ui 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +157 -2
- package/dist/components/assist/AssistMark.d.ts +64 -0
- package/dist/components/assist/AssistMark.js +108 -0
- package/dist/components/assist/AssistPending.d.ts +24 -0
- package/dist/components/assist/AssistPending.js +26 -0
- package/dist/components/assist/AssistSuggestion.d.ts +42 -0
- package/dist/components/assist/AssistSuggestion.js +55 -0
- package/dist/components/assist/ChangeReview.d.ts +56 -0
- package/dist/components/assist/ChangeReview.js +121 -0
- package/dist/components/assist/assist.d.ts +77 -0
- package/dist/components/assist/assist.js +87 -0
- package/dist/components/assist/index.d.ts +5 -0
- package/dist/components/assist/index.js +7 -0
- package/dist/components/chatter/Chatter.d.ts +161 -0
- package/dist/components/chatter/Chatter.js +232 -0
- package/dist/components/chatter/Messages.d.ts +66 -0
- package/dist/components/chatter/Messages.js +43 -0
- package/dist/components/chatter/Timeline.d.ts +21 -0
- package/dist/components/chatter/Timeline.js +30 -0
- package/dist/components/chatter/index.d.ts +3 -0
- package/dist/components/chatter/index.js +4 -0
- package/dist/components/form/Form.d.ts +52 -0
- package/dist/components/form/Form.js +49 -0
- package/dist/components/form/Sheet.d.ts +43 -0
- package/dist/components/form/Sheet.js +83 -0
- package/dist/components/form/index.d.ts +2 -0
- package/dist/components/form/index.js +4 -0
- package/dist/components/frame/Chart.d.ts +13 -0
- package/dist/components/frame/Chart.js +24 -0
- package/dist/components/frame/FrameView.d.ts +33 -0
- package/dist/components/frame/FrameView.js +125 -0
- package/dist/components/frame/index.d.ts +3 -0
- package/dist/components/frame/index.js +3 -0
- package/dist/components/frame/model.d.ts +43 -0
- package/dist/components/frame/model.js +101 -0
- package/dist/components/index.d.ts +10 -0
- package/dist/components/index.js +11 -0
- package/dist/components/lines/LineItems.d.ts +74 -0
- package/dist/components/lines/LineItems.js +229 -0
- package/dist/components/lines/index.d.ts +1 -0
- package/dist/components/lines/index.js +2 -0
- package/dist/components/lines/lines.d.ts +23 -0
- package/dist/components/lines/lines.js +26 -0
- package/dist/components/listing/ColumnsMenu.d.ts +20 -0
- package/dist/components/listing/ColumnsMenu.js +46 -0
- package/dist/components/listing/GroupingLevels.d.ts +35 -0
- package/dist/components/listing/GroupingLevels.js +45 -0
- package/dist/components/listing/Listing.d.ts +27 -0
- package/dist/components/listing/Listing.js +86 -0
- package/dist/components/listing/ListingBar.d.ts +73 -0
- package/dist/components/listing/ListingBar.js +105 -0
- package/dist/components/listing/Pagination.d.ts +36 -0
- package/dist/components/listing/Pagination.js +69 -0
- package/dist/components/listing/RecordCard.d.ts +33 -0
- package/dist/components/listing/RecordCard.js +22 -0
- package/dist/components/listing/RecordLink.d.ts +7 -0
- package/dist/components/listing/RecordLink.js +22 -0
- package/dist/components/listing/Row.d.ts +106 -0
- package/dist/components/listing/Row.js +122 -0
- package/dist/components/listing/SortMenu.d.ts +62 -0
- package/dist/components/listing/SortMenu.js +105 -0
- package/dist/components/listing/ViewSwitcher.d.ts +24 -0
- package/dist/components/listing/ViewSwitcher.js +16 -0
- package/dist/components/listing/columns.d.ts +47 -0
- package/dist/components/listing/columns.js +72 -0
- package/dist/components/listing/grouping.d.ts +35 -0
- package/dist/components/listing/grouping.js +47 -0
- package/dist/components/listing/index.d.ts +17 -0
- package/dist/components/listing/index.js +20 -0
- package/dist/components/listing/modelColumns.d.ts +50 -0
- package/dist/components/listing/modelColumns.js +103 -0
- package/dist/components/listing/pages.d.ts +78 -0
- package/dist/components/listing/pages.js +135 -0
- package/dist/components/listing/selection.d.ts +21 -0
- package/dist/components/listing/selection.js +43 -0
- package/dist/components/listing/set.d.ts +48 -0
- package/dist/components/listing/set.js +24 -0
- package/dist/components/listing/tree.d.ts +28 -0
- package/dist/components/listing/tree.js +23 -0
- package/dist/components/navigation/CommandPalette.d.ts +40 -0
- package/dist/components/navigation/CommandPalette.js +114 -0
- package/dist/components/navigation/NavigationTree.d.ts +31 -0
- package/dist/components/navigation/NavigationTree.js +79 -0
- package/dist/components/navigation/commands.d.ts +30 -0
- package/dist/components/navigation/commands.js +30 -0
- package/dist/components/navigation/index.d.ts +3 -0
- package/dist/components/navigation/index.js +4 -0
- package/dist/components/record/Breadcrumb.d.ts +42 -0
- package/dist/components/record/Breadcrumb.js +98 -0
- package/dist/components/record/DirtyActions.d.ts +26 -0
- package/dist/components/record/DirtyActions.js +22 -0
- package/dist/components/record/Pager.d.ts +18 -0
- package/dist/components/record/Pager.js +33 -0
- package/dist/components/record/RecordHeader.d.ts +71 -0
- package/dist/components/record/RecordHeader.js +72 -0
- package/dist/components/record/RecordSkeleton.d.ts +3 -0
- package/dist/components/record/RecordSkeleton.js +21 -0
- package/dist/components/record/SmartButtons.d.ts +35 -0
- package/dist/components/record/SmartButtons.js +38 -0
- package/dist/components/record/StatusBar.d.ts +51 -0
- package/dist/components/record/StatusBar.js +168 -0
- package/dist/components/record/ThumbBar.d.ts +36 -0
- package/dist/components/record/ThumbBar.js +86 -0
- package/dist/components/record/index.d.ts +6 -0
- package/dist/components/record/index.js +8 -0
- package/dist/components/search/CriteriaBuilder.d.ts +58 -0
- package/dist/components/search/CriteriaBuilder.js +278 -0
- package/dist/components/search/CriteriaChips.d.ts +22 -0
- package/dist/components/search/CriteriaChips.js +64 -0
- package/dist/components/search/FilterBuilder.d.ts +142 -0
- package/dist/components/search/FilterBuilder.js +421 -0
- package/dist/components/search/JoinWord.d.ts +8 -0
- package/dist/components/search/JoinWord.js +47 -0
- package/dist/components/search/JsonEditor.d.ts +19 -0
- package/dist/components/search/JsonEditor.js +78 -0
- package/dist/components/search/SearchBar.d.ts +78 -0
- package/dist/components/search/SearchBar.js +387 -0
- package/dist/components/search/criteria.d.ts +116 -0
- package/dist/components/search/criteria.js +159 -0
- package/dist/components/search/datePeriods.d.ts +122 -0
- package/dist/components/search/datePeriods.js +134 -0
- package/dist/components/search/dateRanges.d.ts +54 -0
- package/dist/components/search/dateRanges.js +89 -0
- package/dist/components/search/index.d.ts +9 -0
- package/dist/components/search/index.js +20 -0
- package/dist/components/search/model.d.ts +54 -0
- package/dist/components/search/model.js +37 -0
- package/dist/components/search/query.d.ts +166 -0
- package/dist/components/search/query.js +436 -0
- package/dist/components/shell/AppFrame.d.ts +14 -0
- package/dist/components/shell/AppFrame.js +80 -0
- package/dist/components/shell/TopBar.d.ts +73 -0
- package/dist/components/shell/TopBar.js +54 -0
- package/dist/components/shell/headerSlot.d.ts +17 -0
- package/dist/components/shell/headerSlot.js +21 -0
- package/dist/components/shell/index.d.ts +2 -0
- package/dist/components/shell/index.js +3 -0
- package/dist/foundations/aria.d.ts +14 -0
- package/dist/foundations/aria.js +18 -0
- package/dist/foundations/calendar.d.ts +28 -0
- package/dist/foundations/calendar.js +183 -0
- package/dist/foundations/copy.d.ts +619 -0
- package/dist/foundations/copy.es.d.ts +2 -0
- package/dist/foundations/copy.es.js +377 -0
- package/dist/foundations/copy.js +472 -0
- package/dist/foundations/density.d.ts +16 -0
- package/dist/foundations/density.js +21 -0
- package/dist/foundations/grouping.d.ts +281 -0
- package/dist/foundations/grouping.js +0 -0
- package/dist/foundations/index.d.ts +14 -0
- package/dist/foundations/index.js +30 -0
- package/dist/foundations/layout.d.ts +9 -0
- package/dist/foundations/layout.js +9 -0
- package/dist/foundations/pivot.d.ts +222 -0
- package/dist/foundations/pivot.js +198 -0
- package/dist/foundations/pivotSheet.d.ts +61 -0
- package/dist/foundations/pivotSheet.js +0 -0
- package/dist/foundations/slots.d.ts +67 -0
- package/dist/foundations/slots.js +63 -0
- package/dist/foundations/sticky.d.ts +53 -0
- package/dist/foundations/sticky.js +150 -0
- package/dist/foundations/surface.d.ts +9 -0
- package/dist/foundations/surface.js +26 -0
- package/dist/foundations/theme.d.ts +42 -0
- package/dist/foundations/theme.js +88 -0
- package/dist/foundations/types.d.ts +23 -0
- package/dist/foundations/types.js +50 -0
- package/dist/foundations/variants.d.ts +19 -0
- package/dist/foundations/variants.js +48 -0
- package/dist/foundations/xlsx.d.ts +86 -0
- package/dist/foundations/xlsx.js +0 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +19 -0
- package/dist/primitives/block/Block.d.ts +66 -0
- package/dist/primitives/block/Block.js +34 -0
- package/dist/primitives/block/Line.d.ts +18 -0
- package/dist/primitives/block/Line.js +45 -0
- package/dist/primitives/block/SectionGrid.d.ts +30 -0
- package/dist/primitives/block/SectionGrid.js +29 -0
- package/dist/primitives/block/Steps.d.ts +76 -0
- package/dist/primitives/block/Steps.js +43 -0
- package/dist/primitives/block/index.d.ts +4 -0
- package/dist/primitives/block/index.js +7 -0
- package/dist/primitives/button/Button.d.ts +137 -0
- package/dist/primitives/button/Button.js +234 -0
- package/dist/primitives/button/index.d.ts +1 -0
- package/dist/primitives/button/index.js +2 -0
- package/dist/primitives/cell/CellStack.d.ts +47 -0
- package/dist/primitives/cell/CellStack.js +33 -0
- package/dist/primitives/cell/index.d.ts +1 -0
- package/dist/primitives/cell/index.js +3 -0
- package/dist/primitives/choice/ComboBox.d.ts +16 -0
- package/dist/primitives/choice/ComboBox.js +52 -0
- package/dist/primitives/choice/RadioGroup.d.ts +18 -0
- package/dist/primitives/choice/RadioGroup.js +50 -0
- package/dist/primitives/choice/SearchField.d.ts +24 -0
- package/dist/primitives/choice/SearchField.js +27 -0
- package/dist/primitives/choice/SegmentedControl.d.ts +16 -0
- package/dist/primitives/choice/SegmentedControl.js +69 -0
- package/dist/primitives/choice/Slider.d.ts +16 -0
- package/dist/primitives/choice/Slider.js +46 -0
- package/dist/primitives/choice/Switch.d.ts +14 -0
- package/dist/primitives/choice/Switch.js +65 -0
- package/dist/primitives/choice/ThemeSwitcher.d.ts +10 -0
- package/dist/primitives/choice/ThemeSwitcher.js +25 -0
- package/dist/primitives/choice/index.d.ts +8 -0
- package/dist/primitives/choice/index.js +11 -0
- package/dist/primitives/choice/options.d.ts +28 -0
- package/dist/primitives/choice/options.js +6 -0
- package/dist/primitives/date/Calendar.d.ts +31 -0
- package/dist/primitives/date/Calendar.js +110 -0
- package/dist/primitives/date/DatePicker.d.ts +34 -0
- package/dist/primitives/date/DatePicker.js +99 -0
- package/dist/primitives/date/index.d.ts +2 -0
- package/dist/primitives/date/index.js +4 -0
- package/dist/primitives/dialog/Dialog.d.ts +118 -0
- package/dist/primitives/dialog/Dialog.js +121 -0
- package/dist/primitives/dialog/index.d.ts +1 -0
- package/dist/primitives/dialog/index.js +2 -0
- package/dist/primitives/disclosure/Disclosure.d.ts +36 -0
- package/dist/primitives/disclosure/Disclosure.js +52 -0
- package/dist/primitives/disclosure/index.d.ts +1 -0
- package/dist/primitives/disclosure/index.js +2 -0
- package/dist/primitives/field/Checks.d.ts +57 -0
- package/dist/primitives/field/Checks.js +72 -0
- package/dist/primitives/field/DropZone.d.ts +42 -0
- package/dist/primitives/field/DropZone.js +52 -0
- package/dist/primitives/field/Field.d.ts +166 -0
- package/dist/primitives/field/Field.js +240 -0
- package/dist/primitives/field/Suggest.d.ts +48 -0
- package/dist/primitives/field/Suggest.js +43 -0
- package/dist/primitives/field/aria.d.ts +61 -0
- package/dist/primitives/field/aria.js +70 -0
- package/dist/primitives/field/context.d.ts +30 -0
- package/dist/primitives/field/context.js +13 -0
- package/dist/primitives/field/index.d.ts +7 -0
- package/dist/primitives/field/index.js +11 -0
- package/dist/primitives/field/layout.d.ts +24 -0
- package/dist/primitives/field/layout.js +10 -0
- package/dist/primitives/field/mode.d.ts +9 -0
- package/dist/primitives/field/mode.js +13 -0
- package/dist/primitives/hint/Hint.d.ts +31 -0
- package/dist/primitives/hint/Hint.js +42 -0
- package/dist/primitives/hint/index.d.ts +1 -0
- package/dist/primitives/hint/index.js +1 -0
- package/dist/primitives/index.d.ts +19 -0
- package/dist/primitives/index.js +20 -0
- package/dist/primitives/menu/Menu.d.ts +100 -0
- package/dist/primitives/menu/Menu.js +59 -0
- package/dist/primitives/menu/index.d.ts +1 -0
- package/dist/primitives/menu/index.js +2 -0
- package/dist/primitives/notice/Callout.d.ts +49 -0
- package/dist/primitives/notice/Callout.js +28 -0
- package/dist/primitives/notice/Notice.d.ts +45 -0
- package/dist/primitives/notice/Notice.js +61 -0
- package/dist/primitives/notice/Toast.d.ts +55 -0
- package/dist/primitives/notice/Toast.js +142 -0
- package/dist/primitives/notice/index.d.ts +3 -0
- package/dist/primitives/notice/index.js +5 -0
- package/dist/primitives/overlay/Popover.d.ts +6 -0
- package/dist/primitives/overlay/Popover.js +27 -0
- package/dist/primitives/overlay/PopoverTrigger.d.ts +55 -0
- package/dist/primitives/overlay/PopoverTrigger.js +38 -0
- package/dist/primitives/overlay/Tooltip.d.ts +38 -0
- package/dist/primitives/overlay/Tooltip.js +74 -0
- package/dist/primitives/overlay/index.d.ts +3 -0
- package/dist/primitives/overlay/index.js +5 -0
- package/dist/primitives/picker/Picker.d.ts +48 -0
- package/dist/primitives/picker/Picker.js +150 -0
- package/dist/primitives/picker/index.d.ts +1 -0
- package/dist/primitives/picker/index.js +2 -0
- package/dist/primitives/surface/Peek.d.ts +60 -0
- package/dist/primitives/surface/Peek.js +71 -0
- package/dist/primitives/surface/index.d.ts +1 -0
- package/dist/primitives/surface/index.js +2 -0
- package/dist/primitives/table/CellEditor.d.ts +14 -0
- package/dist/primitives/table/CellEditor.js +83 -0
- package/dist/primitives/table/DataTable.d.ts +135 -0
- package/dist/primitives/table/DataTable.js +244 -0
- package/dist/primitives/table/EditableTable.d.ts +96 -0
- package/dist/primitives/table/EditableTable.js +423 -0
- package/dist/primitives/table/PivotTable.d.ts +14 -0
- package/dist/primitives/table/PivotTable.js +0 -0
- package/dist/primitives/table/ReportTable.d.ts +38 -0
- package/dist/primitives/table/ReportTable.js +106 -0
- package/dist/primitives/table/RowActions.d.ts +16 -0
- package/dist/primitives/table/RowActions.js +28 -0
- package/dist/primitives/table/SheetTable.d.ts +70 -0
- package/dist/primitives/table/SheetTable.js +197 -0
- package/dist/primitives/table/Table.d.ts +106 -0
- package/dist/primitives/table/Table.js +116 -0
- package/dist/primitives/table/index.d.ts +7 -0
- package/dist/primitives/table/index.js +9 -0
- package/dist/primitives/tabs/Tabs.d.ts +91 -0
- package/dist/primitives/tabs/Tabs.js +56 -0
- package/dist/primitives/tabs/index.d.ts +1 -0
- package/dist/primitives/tabs/index.js +1 -0
- package/dist/primitives/tag/Tag.d.ts +36 -0
- package/dist/primitives/tag/Tag.js +68 -0
- package/dist/primitives/tag/Tags.d.ts +57 -0
- package/dist/primitives/tag/Tags.js +104 -0
- package/dist/primitives/tag/TagsField.d.ts +32 -0
- package/dist/primitives/tag/TagsField.js +182 -0
- package/dist/primitives/tag/choices.d.ts +38 -0
- package/dist/primitives/tag/choices.js +26 -0
- package/dist/primitives/tag/index.d.ts +4 -0
- package/dist/primitives/tag/index.js +8 -0
- package/dist/primitives/toolbar/Toolbar.d.ts +13 -0
- package/dist/primitives/toolbar/Toolbar.js +36 -0
- package/dist/primitives/toolbar/index.d.ts +1 -0
- package/dist/primitives/toolbar/index.js +2 -0
- package/dist/primitives/value/Copyable.d.ts +37 -0
- package/dist/primitives/value/Copyable.js +50 -0
- package/dist/primitives/value/Fecha.d.ts +12 -0
- package/dist/primitives/value/Fecha.js +80 -0
- package/dist/primitives/value/Meter.d.ts +29 -0
- package/dist/primitives/value/Meter.js +46 -0
- package/dist/primitives/value/Metric.d.ts +42 -0
- package/dist/primitives/value/Metric.js +38 -0
- package/dist/primitives/value/Person.d.ts +90 -0
- package/dist/primitives/value/Person.js +208 -0
- package/dist/primitives/value/Relation.d.ts +69 -0
- package/dist/primitives/value/Relation.js +57 -0
- package/dist/primitives/value/Totals.d.ts +26 -0
- package/dist/primitives/value/Totals.js +4 -0
- package/dist/primitives/value/Value.d.ts +77 -0
- package/dist/primitives/value/Value.js +119 -0
- package/dist/primitives/value/index.d.ts +8 -0
- package/dist/primitives/value/index.js +9 -0
- package/dist/tokens.css +1025 -0
- package/dist/views/index.d.ts +2 -0
- package/dist/views/index.js +3 -0
- package/dist/views/list/List.d.ts +113 -0
- package/dist/views/list/List.js +97 -0
- package/dist/views/list/ListView.d.ts +6 -0
- package/dist/views/list/ListView.js +269 -0
- package/dist/views/list/columns.d.ts +18 -0
- package/dist/views/list/columns.js +27 -0
- package/dist/views/list/derive.d.ts +76 -0
- package/dist/views/list/derive.js +181 -0
- package/dist/views/list/index.d.ts +2 -0
- package/dist/views/list/index.js +4 -0
- package/dist/views/list/presentations.d.ts +39 -0
- package/dist/views/list/presentations.js +103 -0
- package/dist/views/list/types.d.ts +272 -0
- package/dist/views/list/types.js +1 -0
- package/dist/views/list/useNarrowList.d.ts +17 -0
- package/dist/views/list/useNarrowList.js +32 -0
- package/dist/views/record/Record.d.ts +126 -0
- package/dist/views/record/Record.js +141 -0
- package/dist/views/record/index.d.ts +1 -0
- package/dist/views/record/index.js +2 -0
- package/package.json +109 -4
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { appliedMessage, applyLabel, applyWithUndo, changeKind, chosenState, initiallyChosen, toggleChosen, } from "./assist.js";
|
|
4
|
+
import { AssistLabel, AssistSourceLink } from "./AssistMark.js";
|
|
5
|
+
import { fillCopy, useUiCopy } from "../../foundations/copy.js";
|
|
6
|
+
import { flatten, propsOf, slot, slotOf } from "../../foundations/slots.js";
|
|
7
|
+
import { Button } from "../../primitives/button/Button.js";
|
|
8
|
+
import { Dialog } from "../../primitives/dialog/Dialog.js";
|
|
9
|
+
import { Checkbox } from "../../primitives/field/Checks.js";
|
|
10
|
+
import { useToastIfAny } from "../../primitives/notice/Toast.js";
|
|
11
|
+
import { Tag } from "../../primitives/tag/Tag.js";
|
|
12
|
+
import { ArrowRight } from "lucide-react";
|
|
13
|
+
import { useId, useState } from "react";
|
|
14
|
+
const Group = slot("group", (_props) => null);
|
|
15
|
+
const Change = slot("change", (_props) => null);
|
|
16
|
+
/** The groups as written. Changes written outside a group form an untitled one in their
|
|
17
|
+
* place, so a short review is just its changes. */
|
|
18
|
+
function readTree(children) {
|
|
19
|
+
const out = [];
|
|
20
|
+
let loose = null;
|
|
21
|
+
for (const node of flatten(children)) {
|
|
22
|
+
const name = slotOf(node);
|
|
23
|
+
if (name === "group") {
|
|
24
|
+
loose = null;
|
|
25
|
+
const props = propsOf(node);
|
|
26
|
+
out.push({
|
|
27
|
+
...(props.title === undefined ? {} : { title: props.title }),
|
|
28
|
+
changes: flatten(props.children)
|
|
29
|
+
.filter((one) => slotOf(one) === "change")
|
|
30
|
+
.map((one) => propsOf(one)),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
else if (name === "change") {
|
|
34
|
+
if (loose === null) {
|
|
35
|
+
loose = { changes: [] };
|
|
36
|
+
out.push(loose);
|
|
37
|
+
}
|
|
38
|
+
loose.changes.push(propsOf(node));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
/** What both faces share: the tree read once, the chosen ids, and «apply». */
|
|
44
|
+
function useReview({ children, chosen, onChosenChange, onApply }) {
|
|
45
|
+
const copy = useUiCopy();
|
|
46
|
+
const toasts = useToastIfAny();
|
|
47
|
+
const groups = readTree(children);
|
|
48
|
+
const all = groups.flatMap((g) => g.changes);
|
|
49
|
+
const order = all.map((c) => c.id);
|
|
50
|
+
const [own, setOwn] = useState(() => initiallyChosen(all));
|
|
51
|
+
const current = chosen === undefined ? own : order.filter((id) => chosen.includes(id));
|
|
52
|
+
const choose = (next) => {
|
|
53
|
+
if (chosen === undefined)
|
|
54
|
+
setOwn(next);
|
|
55
|
+
onChosenChange?.(next);
|
|
56
|
+
};
|
|
57
|
+
const apply = () => {
|
|
58
|
+
const ids = current;
|
|
59
|
+
applyWithUndo(() => onApply(ids), toasts, appliedMessage(copy, ids.length), copy.assist.undo);
|
|
60
|
+
};
|
|
61
|
+
return { groups, order, current, choose, apply };
|
|
62
|
+
}
|
|
63
|
+
/** One change: its check (named by its label), the kind in words, and before → after. */
|
|
64
|
+
function ChangeRow({ change, on, onToggle, }) {
|
|
65
|
+
const copy = useUiCopy();
|
|
66
|
+
const valuesId = useId();
|
|
67
|
+
const kind = changeKind(change);
|
|
68
|
+
const word = {
|
|
69
|
+
add: copy.assist.added,
|
|
70
|
+
remove: copy.assist.removed,
|
|
71
|
+
change: copy.assist.changed,
|
|
72
|
+
}[kind];
|
|
73
|
+
const empty = _jsx("span", { className: "text-muted italic", children: copy.assist.empty });
|
|
74
|
+
const before = change.before === null ? empty : change.before;
|
|
75
|
+
return (_jsxs("li", { "data-change": kind, className: "flex min-w-0 flex-col gap-1 border-b border-line px-4 py-2.5 last:border-b-0 @lg:flex-row @lg:items-start @lg:gap-4", children: [_jsxs("span", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [_jsxs("span", { className: "flex min-w-0 flex-wrap items-center gap-2", children: [_jsx(Checkbox, { checked: on, onChange: onToggle, label: change.label, describedBy: valuesId }), _jsx(Tag, { children: word })] }), change.detail === undefined ? null : (_jsx("span", { className: "pl-6.5 text-micro text-muted", children: change.detail })), change.source === undefined ? null : (_jsx("span", { className: "pl-6.5", children: _jsx(AssistSourceLink, { source: change.source }) }))] }), _jsxs("span", { id: valuesId, className: "flex min-w-0 flex-wrap items-baseline gap-x-1.5 pl-6.5 text-meta tabular-nums @lg:w-2/5 @lg:shrink-0 @lg:justify-end @lg:pl-0 @lg:text-right", children: [kind === "add" ? null : (_jsxs(_Fragment, { children: [_jsxs("span", { className: "sr-only", children: [copy.assist.from, " "] }), _jsx("span", { className: `text-muted ${kind === "remove" ? "line-through" : ""}`, children: before })] })), kind === "remove" ? null : (_jsxs(_Fragment, { children: [kind === "add" ? null : (_jsx(ArrowRight, { className: "size-(--size-icon-sm) shrink-0 self-center text-muted", strokeWidth: 2, "aria-hidden": true })), _jsxs("span", { className: "sr-only", children: [copy.assist.to, " "] }), _jsx("span", { className: "font-medium text-ink", children: change.after })] }))] })] }));
|
|
76
|
+
}
|
|
77
|
+
/** «Todos los cambios», the count, and the groups. */
|
|
78
|
+
function ReviewBody({ review }) {
|
|
79
|
+
const copy = useUiCopy();
|
|
80
|
+
const { groups, order, current, choose } = review;
|
|
81
|
+
const state = chosenState(order, current);
|
|
82
|
+
return (_jsxs("div", { className: "@container flex min-w-0 flex-col", children: [_jsxs("div", { className: "flex min-w-0 flex-wrap items-center justify-between gap-x-3 gap-y-1 border-b border-line px-4 py-2", children: [_jsx(Checkbox, { checked: state === "all", mixed: state === "some", label: copy.assist.all, onChange: (on) => choose(on ? [...order] : []) }), _jsx("span", { className: "text-micro text-muted tabular-nums", children: fillCopy(copy.assist.chosen, { count: current.length, total: order.length }) })] }), groups.map((group, i) => (_jsxs("section", { className: "flex min-w-0 flex-col", children: [group.title === undefined ? null : (_jsx("h4", { className: "px-4 pt-3 pb-1 text-micro font-medium text-muted", children: group.title })), _jsx("ul", { className: "flex min-w-0 flex-col", children: group.changes.map((change) => (_jsx(ChangeRow, { change: change, on: current.includes(change.id), onToggle: (on) => choose(toggleChosen(order, current, change.id, on)) }, change.id))) })] }, group.title ?? i)))] }));
|
|
83
|
+
}
|
|
84
|
+
/** «Elegí al menos un cambio.», «Descartar» and «Aplicar N cambios». */
|
|
85
|
+
function ReviewActions({ review, emphasis, onDiscard, after, }) {
|
|
86
|
+
const copy = useUiCopy();
|
|
87
|
+
const count = review.current.length;
|
|
88
|
+
return (_jsxs(_Fragment, { children: [count > 0 ? null : (
|
|
89
|
+
/* The reason next to the disabled button: a grey «Aplicar» with no explanation is a
|
|
90
|
+
dead end (the same rule as the wizard's «siguiente»). */
|
|
91
|
+
_jsx("span", { className: "mr-auto min-w-0 text-micro text-warn", children: copy.assist.noneChosen })), _jsx(Button, { role: "ghost", size: "sm", onClick: () => {
|
|
92
|
+
onDiscard();
|
|
93
|
+
after?.();
|
|
94
|
+
}, children: copy.assist.discard }), _jsx(Button, { role: emphasis, size: "sm", disabled: count === 0, onClick: () => {
|
|
95
|
+
review.apply();
|
|
96
|
+
after?.();
|
|
97
|
+
}, children: applyLabel(copy, count) })] }));
|
|
98
|
+
}
|
|
99
|
+
/** The review inside the page: a dashed card in the record it would change. */
|
|
100
|
+
export function ChangeReview(props) {
|
|
101
|
+
const { title, summary, source, onDiscard, emphasis = "primary" } = props;
|
|
102
|
+
const copy = useUiCopy();
|
|
103
|
+
const review = useReview(props);
|
|
104
|
+
const titleId = useId();
|
|
105
|
+
return (_jsxs("section", { "aria-labelledby": titleId, "data-assist": "review", className: "flex min-w-0 flex-col overflow-clip rounded-box border border-dashed border-info/40 bg-surface", children: [_jsxs("header", { className: "flex min-w-0 flex-col gap-1 border-b border-line px-4 py-3", children: [_jsxs("span", { className: "flex min-w-0 flex-wrap items-baseline gap-x-3 gap-y-1", children: [_jsx(AssistLabel, {}), source === undefined ? null : _jsx(AssistSourceLink, { source: source })] }), _jsx("h3", { id: titleId, className: "text-lead font-semibold text-ink", children: title ?? copy.assist.review }), summary === undefined ? null : (_jsx("p", { className: "max-w-prose text-meta text-muted", children: summary }))] }), _jsx(ReviewBody, { review: review }), _jsx("footer", { className: "flex min-w-0 flex-wrap items-center justify-end gap-2 border-t border-line bg-ground px-4 py-2.5", children: _jsx(ReviewActions, { review: review, emphasis: emphasis, onDiscard: onDiscard }) })] }));
|
|
106
|
+
}
|
|
107
|
+
ChangeReview.Group = Group;
|
|
108
|
+
ChangeReview.Change = Change;
|
|
109
|
+
/**
|
|
110
|
+
* The same review, in a `Dialog`: opened on demand — from a notice that says «el asistente
|
|
111
|
+
* encontró la orden de compra» — and a bottom sheet on a phone. «Aplicar» is the dialog's
|
|
112
|
+
* primary; closing without choosing keeps the proposal for later.
|
|
113
|
+
*/
|
|
114
|
+
export function ChangeReviewDialog(props) {
|
|
115
|
+
const { open, onClose, context, title, summary, source, onDiscard } = props;
|
|
116
|
+
const copy = useUiCopy();
|
|
117
|
+
const review = useReview(props);
|
|
118
|
+
return (_jsx(Dialog, { open: open, onClose: onClose, title: title ?? copy.assist.review, ...(summary === undefined ? {} : { subtitle: summary }), ...(context === undefined ? {} : { context }), footer: _jsx(ReviewActions, { review: review, emphasis: "primary", onDiscard: onDiscard, after: onClose }), children: _jsxs("div", { className: "-mx-4 -my-4 flex min-w-0 flex-col", children: [_jsxs("span", { className: "flex min-w-0 flex-wrap items-baseline gap-x-3 gap-y-1 border-b border-line px-4 py-2.5", children: [_jsx(AssistLabel, {}), source === undefined ? null : _jsx(AssistSourceLink, { source: source })] }), _jsx(ReviewBody, { review: review })] }) }));
|
|
119
|
+
}
|
|
120
|
+
ChangeReviewDialog.Group = Group;
|
|
121
|
+
ChangeReviewDialog.Change = Change;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { type UiCopy } from "../../foundations/copy.js";
|
|
2
|
+
import type { ToastSpec } from "../../primitives/notice/Toast.js";
|
|
3
|
+
import { type LucideIcon } from "lucide-react";
|
|
4
|
+
import type { ReactNode } from "react";
|
|
5
|
+
/** The one mark of the assistant, everywhere it appears: the suggestion, the review, the
|
|
6
|
+
* marker on a value, the event in the history. A robot and not sparkles — it says «an
|
|
7
|
+
* automated assistant did this», which is a fact, not a celebration.
|
|
8
|
+
*
|
|
9
|
+
* Exported so a screen that composes the assistant into a piece of its own — a `Callout`,
|
|
10
|
+
* a `ChatterEvent` — uses the same mark and not a second one. */
|
|
11
|
+
export declare const ASSIST_ICON: LucideIcon;
|
|
12
|
+
/** Where a suggestion or a change comes from: «orden de compra PO/2026/0042», «las últimas
|
|
13
|
+
* 5 facturas del cliente». With `href` it is a real link; with `onOpen`, a button that the
|
|
14
|
+
* screen answers. Without either it is said, not linked. */
|
|
15
|
+
export type AssistSource = {
|
|
16
|
+
label: string;
|
|
17
|
+
href?: string;
|
|
18
|
+
onOpen?: () => void;
|
|
19
|
+
};
|
|
20
|
+
/** What a value set by the assistant carries afterwards: when, from what, and who said yes. */
|
|
21
|
+
export type AssistOrigin = {
|
|
22
|
+
/** When the assistant set it (or when it was accepted). */
|
|
23
|
+
at?: Date | string | number;
|
|
24
|
+
source?: AssistSource;
|
|
25
|
+
/** The person who accepted it. The assistant proposes; a person decides. */
|
|
26
|
+
acceptedBy?: string;
|
|
27
|
+
};
|
|
28
|
+
/** Applying something the assistant proposed. It may return the function that reverts it:
|
|
29
|
+
* with one, the screen gets a «Deshacer» notice; without one, nothing to undo is offered. */
|
|
30
|
+
export type AssistApply<A extends unknown[] = []> = (...args: A) => (() => void) | void;
|
|
31
|
+
/** The part of the toast API the family uses — the optional toast hook gives it, or `null`. */
|
|
32
|
+
export type AssistToasts = {
|
|
33
|
+
show: (spec: ToastSpec) => number;
|
|
34
|
+
} | null;
|
|
35
|
+
/**
|
|
36
|
+
* Runs `apply` and, when it returns an undo and there is a notice stack, offers it.
|
|
37
|
+
*
|
|
38
|
+
* Context: undo over confirm (docs/PRINCIPLES.md §7). Accepting a suggestion does not ask
|
|
39
|
+
* «¿estás seguro?» — the change happens at once, and the notice carries the way back. The
|
|
40
|
+
* notice is `good` because the change worked; the undo lives in its action, which is the
|
|
41
|
+
* same «Deshacer» a removed line or a registered payment offers.
|
|
42
|
+
*/
|
|
43
|
+
export declare function applyWithUndo(apply: () => (() => void) | void, toasts: AssistToasts, message: string, undoLabel: string): void;
|
|
44
|
+
/** The notice after accepting a suggestion — naming the field when the screen said which. */
|
|
45
|
+
export declare function acceptedMessage(copy: UiCopy, field?: string): string;
|
|
46
|
+
export type ChangeKind = "add" | "remove" | "change";
|
|
47
|
+
/** One proposed change, as plain data — what `ChangeReview.Change` is written with. */
|
|
48
|
+
export type ProposedChange = {
|
|
49
|
+
id: string;
|
|
50
|
+
/** What changes: the field's label, or the line's name. */
|
|
51
|
+
label: string;
|
|
52
|
+
/** The value now. Absent = the thing does not exist yet (`add`). `null` = an empty field. */
|
|
53
|
+
before?: ReactNode;
|
|
54
|
+
/** The value after. Absent = the thing goes away (`remove`). */
|
|
55
|
+
after?: ReactNode;
|
|
56
|
+
/** Forces the kind; by default it follows from `before` and `after`. */
|
|
57
|
+
kind?: ChangeKind;
|
|
58
|
+
/** A second line under the label: the product's code, «40 bolsas × Bs. 58,00». */
|
|
59
|
+
detail?: ReactNode;
|
|
60
|
+
/** Where THIS change comes from, when it is not the review's own source. */
|
|
61
|
+
source?: AssistSource;
|
|
62
|
+
/** Proposed but not chosen when the review opens — the assistant is less sure of it. */
|
|
63
|
+
unchosen?: boolean;
|
|
64
|
+
};
|
|
65
|
+
/** The kind of a change, from what it has: no `before` → new, no `after` → removed. */
|
|
66
|
+
export declare function changeKind(change: Pick<ProposedChange, "before" | "after" | "kind">): ChangeKind;
|
|
67
|
+
/** The ids chosen when a review opens: all of them but the ones marked `unchosen`. */
|
|
68
|
+
export declare function initiallyChosen(changes: readonly ProposedChange[]): string[];
|
|
69
|
+
/** Turns one change on or off, and returns the chosen ids IN THE ORDER the review lists
|
|
70
|
+
* them — so what `onApply` receives reads like the screen, not like the clicks. */
|
|
71
|
+
export declare function toggleChosen(order: readonly string[], chosen: readonly string[], id: string, on: boolean): string[];
|
|
72
|
+
/** Whether the «all» check is on, off, or mixed. */
|
|
73
|
+
export declare function chosenState(order: readonly string[], chosen: readonly string[]): "all" | "some" | "none";
|
|
74
|
+
/** The label of the apply button, with the count — a button that says how much it does. */
|
|
75
|
+
export declare function applyLabel(copy: UiCopy, count: number): string;
|
|
76
|
+
/** The notice after applying. */
|
|
77
|
+
export declare function appliedMessage(copy: UiCopy, count: number): string;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { fillCopy } from "../../foundations/copy.js";
|
|
2
|
+
import { Bot } from "lucide-react";
|
|
3
|
+
/* ==================================================================== *
|
|
4
|
+
* THE ASSISTANT'S CONTRACT — what every piece of this family agrees on.
|
|
5
|
+
*
|
|
6
|
+
* An assistant that acts inside a business screen is trusted for one reason:
|
|
7
|
+
* whoever works the screen can always answer three questions about what it did.
|
|
8
|
+
*
|
|
9
|
+
* where did it get that? → a SOURCE, a link to the record it read
|
|
10
|
+
* what exactly will change? → the value, or the before → after
|
|
11
|
+
* how do I take it back? → apply returns its UNDO
|
|
12
|
+
*
|
|
13
|
+
* So a source is plain data (a label and where it goes), and applying anything
|
|
14
|
+
* — accepting a suggestion, applying a set of changes — is a function the
|
|
15
|
+
* screen writes and that RETURNS the function that reverts it. The family turns
|
|
16
|
+
* that return into the system's «Deshacer» notice; it never applies anything by
|
|
17
|
+
* itself, because it does not know what an invoice is.
|
|
18
|
+
*
|
|
19
|
+
* Pure on purpose (no hooks, no "use client"): the components call these, and
|
|
20
|
+
* the tests do too.
|
|
21
|
+
* ==================================================================== */
|
|
22
|
+
/** The one mark of the assistant, everywhere it appears: the suggestion, the review, the
|
|
23
|
+
* marker on a value, the event in the history. A robot and not sparkles — it says «an
|
|
24
|
+
* automated assistant did this», which is a fact, not a celebration.
|
|
25
|
+
*
|
|
26
|
+
* Exported so a screen that composes the assistant into a piece of its own — a `Callout`,
|
|
27
|
+
* a `ChatterEvent` — uses the same mark and not a second one. */
|
|
28
|
+
export const ASSIST_ICON = Bot;
|
|
29
|
+
/**
|
|
30
|
+
* Runs `apply` and, when it returns an undo and there is a notice stack, offers it.
|
|
31
|
+
*
|
|
32
|
+
* Context: undo over confirm (docs/PRINCIPLES.md §7). Accepting a suggestion does not ask
|
|
33
|
+
* «¿estás seguro?» — the change happens at once, and the notice carries the way back. The
|
|
34
|
+
* notice is `good` because the change worked; the undo lives in its action, which is the
|
|
35
|
+
* same «Deshacer» a removed line or a registered payment offers.
|
|
36
|
+
*/
|
|
37
|
+
export function applyWithUndo(apply, toasts, message, undoLabel) {
|
|
38
|
+
const undo = apply();
|
|
39
|
+
if (typeof undo !== "function" || toasts === null)
|
|
40
|
+
return;
|
|
41
|
+
toasts.show({ message, tone: "good", action: { label: undoLabel, onSelect: undo } });
|
|
42
|
+
}
|
|
43
|
+
/** The notice after accepting a suggestion — naming the field when the screen said which. */
|
|
44
|
+
export function acceptedMessage(copy, field) {
|
|
45
|
+
return field === undefined
|
|
46
|
+
? copy.assist.accepted
|
|
47
|
+
: fillCopy(copy.assist.acceptedIn, { field });
|
|
48
|
+
}
|
|
49
|
+
/** The kind of a change, from what it has: no `before` → new, no `after` → removed. */
|
|
50
|
+
export function changeKind(change) {
|
|
51
|
+
if (change.kind !== undefined)
|
|
52
|
+
return change.kind;
|
|
53
|
+
if (change.before === undefined)
|
|
54
|
+
return "add";
|
|
55
|
+
if (change.after === undefined)
|
|
56
|
+
return "remove";
|
|
57
|
+
return "change";
|
|
58
|
+
}
|
|
59
|
+
/** The ids chosen when a review opens: all of them but the ones marked `unchosen`. */
|
|
60
|
+
export function initiallyChosen(changes) {
|
|
61
|
+
return changes.filter((c) => c.unchosen !== true).map((c) => c.id);
|
|
62
|
+
}
|
|
63
|
+
/** Turns one change on or off, and returns the chosen ids IN THE ORDER the review lists
|
|
64
|
+
* them — so what `onApply` receives reads like the screen, not like the clicks. */
|
|
65
|
+
export function toggleChosen(order, chosen, id, on) {
|
|
66
|
+
const next = new Set(chosen);
|
|
67
|
+
if (on)
|
|
68
|
+
next.add(id);
|
|
69
|
+
else
|
|
70
|
+
next.delete(id);
|
|
71
|
+
return order.filter((one) => next.has(one));
|
|
72
|
+
}
|
|
73
|
+
/** Whether the «all» check is on, off, or mixed. */
|
|
74
|
+
export function chosenState(order, chosen) {
|
|
75
|
+
const count = order.filter((id) => chosen.includes(id)).length;
|
|
76
|
+
if (count === 0)
|
|
77
|
+
return "none";
|
|
78
|
+
return count === order.length ? "all" : "some";
|
|
79
|
+
}
|
|
80
|
+
/** The label of the apply button, with the count — a button that says how much it does. */
|
|
81
|
+
export function applyLabel(copy, count) {
|
|
82
|
+
return count === 1 ? copy.assist.applyOne : fillCopy(copy.assist.applyMany, { count });
|
|
83
|
+
}
|
|
84
|
+
/** The notice after applying. */
|
|
85
|
+
export function appliedMessage(copy, count) {
|
|
86
|
+
return count === 1 ? copy.assist.appliedOne : fillCopy(copy.assist.appliedMany, { count });
|
|
87
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { ASSIST_ICON, type AssistApply, type AssistOrigin, type AssistSource, type ChangeKind, type ProposedChange, } from "./assist.js";
|
|
2
|
+
export { AssistedValue, assistEvent, AssistLabel, AssistMark, type AssistMarkProps, AssistSourceLink, } from "./AssistMark.js";
|
|
3
|
+
export { AssistPending, type AssistPendingProps, } from "./AssistPending.js";
|
|
4
|
+
export { AssistSuggestion, type AssistSuggestionProps, } from "./AssistSuggestion.js";
|
|
5
|
+
export { ChangeReview, ChangeReviewDialog, type ChangeReviewDialogProps, type ChangeReviewProps, } from "./ChangeReview.js";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/* The assistant inside a business screen: a suggestion with its source, a preview of the
|
|
2
|
+
changes it proposes, the mark it leaves on what it set, and its working state. */
|
|
3
|
+
export { ASSIST_ICON, } from "./assist.js";
|
|
4
|
+
export { AssistedValue, assistEvent, AssistLabel, AssistMark, AssistSourceLink, } from "./AssistMark.js";
|
|
5
|
+
export { AssistPending, } from "./AssistPending.js";
|
|
6
|
+
export { AssistSuggestion, } from "./AssistSuggestion.js";
|
|
7
|
+
export { ChangeReview, ChangeReviewDialog, } from "./ChangeReview.js";
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import type { Message } from "./Messages.js";
|
|
2
|
+
import type { Tone } from "../../foundations/types.js";
|
|
3
|
+
import type { LucideIcon } from "lucide-react";
|
|
4
|
+
import { type ReactNode } from "react";
|
|
5
|
+
/**
|
|
6
|
+
* What a record has been through: what the system logged and what people wrote,
|
|
7
|
+
* in one place.
|
|
8
|
+
*
|
|
9
|
+
* Odoo calls it the chatter and the idea is the one worth taking: a record's
|
|
10
|
+
* history is not one more section of the record — it is the CONTEXT for
|
|
11
|
+
* everything else on the screen. Reading «the assessment failed» while looking
|
|
12
|
+
* at the inputs that produced it is the whole point.
|
|
13
|
+
*
|
|
14
|
+
* ── The merge is the default, and that is the decision ──────────────
|
|
15
|
+
*
|
|
16
|
+
* **Everything together comes first**, because the question somebody actually
|
|
17
|
+
* has is «what happened here», not «what happened here *according to the
|
|
18
|
+
* system*». A payment that bounced and the message somebody left about it are one
|
|
19
|
+
* story; splitting them by who wrote them makes the reader join them back by
|
|
20
|
+
* hand, comparing two timestamps in two lists.
|
|
21
|
+
*
|
|
22
|
+
* The other two tabs are for when you already know which half you want: «who
|
|
23
|
+
* said what» and «what ran». They are a narrowing of the first, never a
|
|
24
|
+
* different content.
|
|
25
|
+
*
|
|
26
|
+
* ── Why the tabs live HERE and not in whoever composes ──────────────
|
|
27
|
+
*
|
|
28
|
+
* They used to be a generic `tabs` array and every screen decided its own: same
|
|
29
|
+
* component, a different chatter on each record. What a record has been through
|
|
30
|
+
* is the same question everywhere, so the answer is the component's, not the
|
|
31
|
+
* screen's. The screen passes what it HAS —messages, events, a composer— and
|
|
32
|
+
* this decides how it is read.
|
|
33
|
+
*
|
|
34
|
+
* A tab with no source behind it is not drawn: with only events there is
|
|
35
|
+
* nothing to merge and nothing to separate, so it shows the stream and no tab
|
|
36
|
+
* bar at all.
|
|
37
|
+
*
|
|
38
|
+
* ── Cut into pages, like any other list ─────────────────────────────
|
|
39
|
+
*
|
|
40
|
+
* A record with six months behind it has hundreds of entries, and a column that
|
|
41
|
+
* only scrolls turns the history into something you give up on. The pages are
|
|
42
|
+
* this component's and not the screen's for the same reason the tabs are: it is
|
|
43
|
+
* the same list on every record.
|
|
44
|
+
*
|
|
45
|
+
* ── Beside on a wide panel, underneath on a narrow one ──────────────
|
|
46
|
+
*
|
|
47
|
+
* It asks `@container`, not `lg:`, for the reason `SectionGrid` already wrote
|
|
48
|
+
* down: with four panels open each one measures 380 px on a 1.600 screen, and a
|
|
49
|
+
* window rule would put a 22rem column inside a 380 px panel. **Whoever mounts
|
|
50
|
+
* it has to declare `@container` on the container** — `Record` does.
|
|
51
|
+
*/
|
|
52
|
+
/**
|
|
53
|
+
* Algo que el sistema anotó.
|
|
54
|
+
*
|
|
55
|
+
* Deliberadamente NO es el evento del producto: lo que una historia necesita
|
|
56
|
+
* para dibujar uno es qué pasó, cuándo, quién, y —cuando las hay— las tres cosas
|
|
57
|
+
* que uno termina yendo a buscar a otro lado.
|
|
58
|
+
*
|
|
59
|
+
* ── Las tres que faltaban, y por qué cada una ───────────────────────
|
|
60
|
+
*
|
|
61
|
+
* by QUIÉN. «se publicó la factura» no dice si la publicó una persona o un
|
|
62
|
+
* cron, y esa es la primera pregunta cuando algo cambió sin aviso.
|
|
63
|
+
* Ausente = lo hizo el sistema, que también es una respuesta.
|
|
64
|
+
*
|
|
65
|
+
* moved DE QUÉ A QUÉ. «el proyecto cambió de estado» obliga a mirar el
|
|
66
|
+
* evento anterior para saber de dónde venía. «borrador →
|
|
67
|
+
* confirmado» se lee de una y es la línea más útil de una historia
|
|
68
|
+
* de gestión.
|
|
69
|
+
*
|
|
70
|
+
* error QUÉ falló, entero y textual. Un evento rojo que dice «falló» sin
|
|
71
|
+
* decir por qué manda a alguien a los logs — y quien mira esta
|
|
72
|
+
* pantalla muchas veces no tiene acceso a los logs.
|
|
73
|
+
*/
|
|
74
|
+
export type ChatterEvent = {
|
|
75
|
+
id: string;
|
|
76
|
+
/** Qué pasó, en una oración y en pasado. */
|
|
77
|
+
label: ReactNode;
|
|
78
|
+
at: Date | string | number;
|
|
79
|
+
tone?: Tone;
|
|
80
|
+
/** Quién lo hizo. Ausente = el sistema. */
|
|
81
|
+
by?: string;
|
|
82
|
+
/** El cambio de estado, cuando lo hubo. `from` ausente = nació así. */
|
|
83
|
+
moved?: {
|
|
84
|
+
from?: string;
|
|
85
|
+
to: string;
|
|
86
|
+
};
|
|
87
|
+
/** Lo que hay que saber además del título, cuando el título no alcanza. */
|
|
88
|
+
detail?: ReactNode;
|
|
89
|
+
/** Lo que falló, textual. Se muestra la PRIMERA línea y el resto se abre: un
|
|
90
|
+
* stack de cuarenta renglones adentro de una historia tapa la historia, y lo
|
|
91
|
+
* que casi siempre alcanza es el primer renglón. */
|
|
92
|
+
error?: string;
|
|
93
|
+
/** El ícono del punto, cuando el tono no dice lo suficiente. Sin esto sale del
|
|
94
|
+
* tono, que es lo que evita un evento rojo con un tilde verde adentro. */
|
|
95
|
+
icon?: LucideIcon;
|
|
96
|
+
};
|
|
97
|
+
export type ChatterProps = {
|
|
98
|
+
/** What people wrote. Absent —not empty— while there is no service behind it:
|
|
99
|
+
* an empty thread says «nobody wrote yet», which is a different claim. */
|
|
100
|
+
messages?: readonly Message[];
|
|
101
|
+
/** What the system logged. */
|
|
102
|
+
events?: readonly ChatterEvent[];
|
|
103
|
+
/** The box to write in, when this record takes messages. It stays on top of
|
|
104
|
+
* the stream: writing is the thing you came to do, reading is what you do
|
|
105
|
+
* next. */
|
|
106
|
+
composer?: ReactNode;
|
|
107
|
+
/** Which tab is showing. Uncontrolled from «everything» when absent. */
|
|
108
|
+
current?: ChatterView;
|
|
109
|
+
onCurrentChange?: (next: ChatterView) => void;
|
|
110
|
+
open: boolean;
|
|
111
|
+
onToggle: () => void;
|
|
112
|
+
label?: string;
|
|
113
|
+
/** Dentro de la hoja del registro: no lleva marco propio, la hoja ya lo
|
|
114
|
+
* tiene. Sin esto es una tarjeta, que es como se ve cuando vive solo. */
|
|
115
|
+
embedded?: boolean;
|
|
116
|
+
/**
|
|
117
|
+
* Dónde vive el chatter respecto del registro.
|
|
118
|
+
*
|
|
119
|
+
* page DEBAJO y a TODO EL ANCHO, como una hoja propia. **Es el
|
|
120
|
+
* default**, y no por comodidad: un formulario necesita su ancho
|
|
121
|
+
* para que los campos no se apilen, y el de al costado se lo come
|
|
122
|
+
* en cuanto la pantalla no sobra. Abajo, la conversación no compite
|
|
123
|
+
* con el registro — es la otra mitad de la página.
|
|
124
|
+
* aside AL COSTADO cuando hay ancho, DEBAJO cuando no. Es para un
|
|
125
|
+
* registro que se LEE más de lo que se edita, donde tener la
|
|
126
|
+
* historia a la vista mientras se mira el dato vale el ancho que
|
|
127
|
+
* cuesta.
|
|
128
|
+
*
|
|
129
|
+
* La diferencia no es cosmética. En `aside` el ancho es escaso, así que el
|
|
130
|
+
* mensaje se lee en una columna de veintipico de caracteres; en `page` hay
|
|
131
|
+
* ancho de sobra y el texto se acota a una medida legible en vez de estirarse
|
|
132
|
+
* de punta a punta, que es donde un párrafo deja de poder seguirse.
|
|
133
|
+
*
|
|
134
|
+
* **Plegar es de pantalla chica.** En `page` el chatter no se pliega cuando
|
|
135
|
+
* hay ancho: plegarlo ahí esconde media pantalla para no ganar nada.
|
|
136
|
+
*/
|
|
137
|
+
layout?: ChatterLayout;
|
|
138
|
+
};
|
|
139
|
+
export type ChatterLayout = "aside" | "page";
|
|
140
|
+
export type ChatterView = "all" | "messages" | "events";
|
|
141
|
+
export declare function Chatter({ messages, events, composer, current, onCurrentChange, open, onToggle, label: ownLabel, layout, embedded, }: ChatterProps): import("react").JSX.Element;
|
|
142
|
+
/** Whether the chatter is open, when the page has no better answer.
|
|
143
|
+
*
|
|
144
|
+
* **Starts folded.** It began the other way round —a history nobody opens is a
|
|
145
|
+
* history nobody reads— and seeing it took the argument apart: open, it holds a
|
|
146
|
+
* 22rem column of context on every record you open, before anybody asked for
|
|
147
|
+
* any. Folded it still says how many there are, which is the half that has to
|
|
148
|
+
* be free; reading them is a click, and a click you chose.
|
|
149
|
+
*
|
|
150
|
+
* It is the same answer for both layouts. A default that depends on the width
|
|
151
|
+
* would mean the same record shows a different screen depending on how many
|
|
152
|
+
* panels happen to be open next to it, and that is not a default, it is a
|
|
153
|
+
* surprise. */
|
|
154
|
+
export declare function useChatter(initial?: boolean): {
|
|
155
|
+
open: boolean;
|
|
156
|
+
toggle: () => void;
|
|
157
|
+
/** Open it without closing it if it already was. What a button from outside
|
|
158
|
+
* needs —«events 12»— when pressing it twice must not hide the very thing
|
|
159
|
+
* it promised to show. */
|
|
160
|
+
show: () => void;
|
|
161
|
+
};
|