@sincpro/web-ui 0.0.0-stage → 0.2.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 +184 -2
- package/dist/components/activity/Activity.d.ts +69 -0
- package/dist/components/activity/Activity.js +279 -0
- package/dist/components/activity/Messages.d.ts +66 -0
- package/dist/components/activity/Messages.js +46 -0
- package/dist/components/activity/Timeline.d.ts +21 -0
- package/dist/components/activity/Timeline.js +30 -0
- package/dist/components/activity/index.d.ts +3 -0
- package/dist/components/activity/index.js +4 -0
- 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/form/Form.d.ts +52 -0
- package/dist/components/form/Form.js +49 -0
- package/dist/components/form/Sheet.d.ts +47 -0
- package/dist/components/form/Sheet.js +90 -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 +111 -0
- package/dist/components/lines/LineItems.js +317 -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 +112 -0
- package/dist/components/listing/ListingBar.js +144 -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 +110 -0
- package/dist/components/listing/Row.js +123 -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 +75 -0
- package/dist/components/record/RecordHeader.js +104 -0
- package/dist/components/record/RecordNotices.d.ts +37 -0
- package/dist/components/record/RecordNotices.js +75 -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 +703 -0
- package/dist/foundations/copy.es.d.ts +2 -0
- package/dist/foundations/copy.es.js +430 -0
- package/dist/foundations/copy.js +520 -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 +19 -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 +33 -0
- package/dist/primitives/choice/SearchField.js +46 -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 +134 -0
- package/dist/primitives/date/DatePicker.d.ts +42 -0
- package/dist/primitives/date/DatePicker.js +103 -0
- package/dist/primitives/date/DateTime.d.ts +35 -0
- package/dist/primitives/date/DateTime.js +100 -0
- package/dist/primitives/date/index.d.ts +3 -0
- package/dist/primitives/date/index.js +6 -0
- package/dist/primitives/dialog/Dialog.d.ts +118 -0
- package/dist/primitives/dialog/Dialog.js +121 -0
- package/dist/primitives/dialog/Drill.d.ts +90 -0
- package/dist/primitives/dialog/Drill.js +172 -0
- package/dist/primitives/dialog/index.d.ts +2 -0
- package/dist/primitives/dialog/index.js +4 -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 +178 -0
- package/dist/primitives/field/Field.js +251 -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/copy.d.ts +16 -0
- package/dist/primitives/field/copy.js +69 -0
- package/dist/primitives/field/index.d.ts +9 -0
- package/dist/primitives/field/index.js +15 -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/field/note.d.ts +55 -0
- package/dist/primitives/field/note.js +117 -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 +20 -0
- package/dist/primitives/index.js +21 -0
- package/dist/primitives/menu/Menu.d.ts +128 -0
- package/dist/primitives/menu/Menu.js +89 -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/Flag.d.ts +13 -0
- package/dist/primitives/notice/Flag.js +31 -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 +4 -0
- package/dist/primitives/notice/index.js +6 -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/Preview.d.ts +19 -0
- package/dist/primitives/overlay/Preview.js +60 -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 +4 -0
- package/dist/primitives/overlay/index.js +7 -0
- package/dist/primitives/picker/Picker.d.ts +89 -0
- package/dist/primitives/picker/Picker.js +204 -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/CellCopy.d.ts +20 -0
- package/dist/primitives/table/CellCopy.js +23 -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 +189 -0
- package/dist/primitives/table/DataTable.js +330 -0
- package/dist/primitives/table/EditableTable.d.ts +191 -0
- package/dist/primitives/table/EditableTable.js +617 -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 +73 -0
- package/dist/primitives/table/SheetTable.js +230 -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 +35 -0
- package/dist/primitives/tag/TagsField.js +186 -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/tree/Tree.d.ts +56 -0
- package/dist/primitives/tree/Tree.js +74 -0
- package/dist/primitives/tree/index.d.ts +1 -0
- package/dist/primitives/tree/index.js +3 -0
- package/dist/primitives/value/Copyable.d.ts +48 -0
- package/dist/primitives/value/Copyable.js +71 -0
- package/dist/primitives/value/Fecha.d.ts +12 -0
- package/dist/primitives/value/Fecha.js +89 -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/ProgressBar.d.ts +31 -0
- package/dist/primitives/value/ProgressBar.js +53 -0
- package/dist/primitives/value/Relation.d.ts +74 -0
- package/dist/primitives/value/Relation.js +74 -0
- package/dist/primitives/value/Totals.d.ts +121 -0
- package/dist/primitives/value/Totals.js +54 -0
- package/dist/primitives/value/Value.d.ts +40 -0
- package/dist/primitives/value/Value.js +80 -0
- package/dist/primitives/value/index.d.ts +9 -0
- package/dist/primitives/value/index.js +10 -0
- package/dist/tokens.css +1062 -0
- package/dist/views/index.d.ts +2 -0
- package/dist/views/index.js +3 -0
- package/dist/views/list/List.d.ts +119 -0
- package/dist/views/list/List.js +101 -0
- package/dist/views/list/ListView.d.ts +6 -0
- package/dist/views/list/ListView.js +295 -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 +87 -0
- package/dist/views/list/derive.js +207 -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 +105 -0
- package/dist/views/list/types.d.ts +284 -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 +145 -0
- package/dist/views/record/Record.js +211 -0
- package/dist/views/record/index.d.ts +1 -0
- package/dist/views/record/index.js +2 -0
- package/package.json +113 -4
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useUiCopy } from "../../foundations/copy.js";
|
|
4
|
+
import { cn, focusRing } from "../../foundations/variants.js";
|
|
5
|
+
import { CheckCircle2, Info, TriangleAlert, X, XCircle } from "lucide-react";
|
|
6
|
+
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, } from "react";
|
|
7
|
+
import { Button as AriaButton, Link as AriaLink, Text, UNSTABLE_Toast as AriaToast, UNSTABLE_ToastContent as ToastContent, UNSTABLE_ToastQueue as ToastQueue, UNSTABLE_ToastRegion as ToastRegion, } from "react-aria-components";
|
|
8
|
+
const Ctx = createContext(null);
|
|
9
|
+
export function useToast() {
|
|
10
|
+
const api = useContext(Ctx);
|
|
11
|
+
if (api === null) {
|
|
12
|
+
throw new Error("useToast needs a <ToastProvider> above it in the tree.");
|
|
13
|
+
}
|
|
14
|
+
return api;
|
|
15
|
+
}
|
|
16
|
+
/** The same API, or `null` when no `ToastProvider` is above. For a component that OFFERS
|
|
17
|
+
* undo when the screen has a notice stack and still renders alone — in a story, in a test,
|
|
18
|
+
* in an app that has not mounted the provider yet. */
|
|
19
|
+
export function useToastIfAny() {
|
|
20
|
+
return useContext(Ctx);
|
|
21
|
+
}
|
|
22
|
+
const ICON = {
|
|
23
|
+
neutral: Info,
|
|
24
|
+
info: Info,
|
|
25
|
+
good: CheckCircle2,
|
|
26
|
+
warn: TriangleAlert,
|
|
27
|
+
bad: XCircle,
|
|
28
|
+
accent: Info,
|
|
29
|
+
};
|
|
30
|
+
const TONE = {
|
|
31
|
+
neutral: "text-muted",
|
|
32
|
+
info: "text-info",
|
|
33
|
+
good: "text-good",
|
|
34
|
+
warn: "text-warn",
|
|
35
|
+
bad: "text-bad",
|
|
36
|
+
accent: "text-ink",
|
|
37
|
+
};
|
|
38
|
+
const LIFE = 8000;
|
|
39
|
+
/** The default: the bad ones do NOT leave on their own. See the note above. */
|
|
40
|
+
function lifeOf(toast) {
|
|
41
|
+
return toast.duration ?? (toast.tone === "bad" ? 0 : LIFE);
|
|
42
|
+
}
|
|
43
|
+
const LINK = "self-start rounded-control text-micro font-medium text-primary transition-colors duration-(--duration-instant) hover:underline hover:underline-offset-4 cursor-pointer outline-offset-1";
|
|
44
|
+
export function ToastProvider({ children }) {
|
|
45
|
+
const [toasts, setToasts] = useState([]);
|
|
46
|
+
const next = useRef(1);
|
|
47
|
+
const dismiss = useCallback((id) => {
|
|
48
|
+
setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
49
|
+
}, []);
|
|
50
|
+
const show = useCallback((spec) => {
|
|
51
|
+
const id = next.current;
|
|
52
|
+
next.current += 1;
|
|
53
|
+
setToasts((prev) => {
|
|
54
|
+
if (spec.key === undefined)
|
|
55
|
+
return [...prev, { ...spec, id }];
|
|
56
|
+
const i = prev.findIndex((t) => t.key === spec.key);
|
|
57
|
+
/* Replaces IN ITS PLACE and not at the end: if the notice jumps to the
|
|
58
|
+
bottom of the stack every time it updates, whoever was reading it loses
|
|
59
|
+
it. */
|
|
60
|
+
if (i < 0)
|
|
61
|
+
return [...prev, { ...spec, id }];
|
|
62
|
+
const n = [...prev];
|
|
63
|
+
n[i] = { ...spec, id };
|
|
64
|
+
return n;
|
|
65
|
+
});
|
|
66
|
+
return id;
|
|
67
|
+
}, []);
|
|
68
|
+
const api = useMemo(() => ({ show, dismiss }), [show, dismiss]);
|
|
69
|
+
return (_jsxs(Ctx.Provider, { value: api, children: [children, _jsx(ToastLayer, { toasts: toasts, onDismiss: dismiss })] }));
|
|
70
|
+
}
|
|
71
|
+
/** The region of a given list of notices. `ToastProvider` draws it from its own state; it is
|
|
72
|
+
* exported so it can be drawn without the context — a story that wants to see the tones
|
|
73
|
+
* together and still.
|
|
74
|
+
*
|
|
75
|
+
* Context: React Aria's queue is the behaviour (timers, pause, landmark, focus) and this
|
|
76
|
+
* list is the content. Each notice in the list is mirrored into the queue once; when the
|
|
77
|
+
* queue closes one — its timer ran out, its × or its action was pressed — `onDismiss`
|
|
78
|
+
* hears it, and when the list drops one the queue closes it. */
|
|
79
|
+
export function ToastLayer({ toasts, onDismiss, }) {
|
|
80
|
+
const copy = useUiCopy();
|
|
81
|
+
const [queue] = useState(() => new ToastQueue());
|
|
82
|
+
const [, setVersion] = useState(0);
|
|
83
|
+
const latest = useRef(onDismiss);
|
|
84
|
+
/* The ids the layer is closing because the LIST dropped them: those must not be reported
|
|
85
|
+
back as if the person had closed them. */
|
|
86
|
+
const dropping = useRef(new Set());
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
latest.current = onDismiss;
|
|
89
|
+
}, [onDismiss]);
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
const wanted = new Set(toasts.map((t) => t.id));
|
|
92
|
+
const queued = new Set(queue.visibleToasts.map((t) => t.content.id));
|
|
93
|
+
for (const queuedToast of queue.visibleToasts) {
|
|
94
|
+
if (!wanted.has(queuedToast.content.id)) {
|
|
95
|
+
dropping.current.add(queuedToast.content.id);
|
|
96
|
+
queue.close(queuedToast.key);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
for (const toast of toasts) {
|
|
100
|
+
if (queued.has(toast.id))
|
|
101
|
+
continue;
|
|
102
|
+
const life = lifeOf(toast);
|
|
103
|
+
queue.add(toast, {
|
|
104
|
+
...(life > 0 ? { timeout: life } : {}),
|
|
105
|
+
onClose: () => {
|
|
106
|
+
if (dropping.current.delete(toast.id))
|
|
107
|
+
return;
|
|
108
|
+
latest.current(toast.id);
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
setVersion((v) => v + 1);
|
|
113
|
+
}, [toasts, queue]);
|
|
114
|
+
const byId = new Map(queue.visibleToasts.map((t) => [t.content.id, t]));
|
|
115
|
+
/* Drawn in the LIST's order, oldest at the bottom: the newest notice appears on top of
|
|
116
|
+
the stack and a replaced one stays where it was. */
|
|
117
|
+
const ordered = [...toasts].reverse().flatMap((t) => {
|
|
118
|
+
const queued = byId.get(t.id);
|
|
119
|
+
return queued === undefined ? [] : [{ toast: t, queued }];
|
|
120
|
+
});
|
|
121
|
+
/* Mounted only while there is something to show. React Aria registers the region as an F6
|
|
122
|
+
landmark when it mounts, and it skips the registration if the region's element does not
|
|
123
|
+
exist yet — which is the case for a region mounted empty (it draws nothing until the
|
|
124
|
+
first notice). Mounting it together with its first notice is what makes F6 reach it. */
|
|
125
|
+
if (ordered.length === 0)
|
|
126
|
+
return null;
|
|
127
|
+
return (_jsx(ToastRegion, { queue: queue, "aria-label": copy.shell.notices,
|
|
128
|
+
/* `inset-x-4` with a fixed width and `ml-auto`: that way the notice rests against the
|
|
129
|
+
right but never runs off the left edge on a phone. */
|
|
130
|
+
className: ({ isFocusVisible }) => cn("fixed inset-x-4 bottom-4 z-50 ml-auto flex w-(--container-toast) max-w-full flex-col gap-2 rounded-panel outline-offset-4", focusRing({ isFocusVisible })), children: ordered.map(({ toast, queued }) => (_jsx(ToastCard, { toast: toast, queued: queued, queue: queue }, queued.key))) }));
|
|
131
|
+
}
|
|
132
|
+
function ToastCard({ toast, queued, queue, }) {
|
|
133
|
+
const copy = useUiCopy();
|
|
134
|
+
const tone = toast.tone ?? "neutral";
|
|
135
|
+
const Icon = ICON[tone];
|
|
136
|
+
const close = () => queue.close(queued.key);
|
|
137
|
+
const action = toast.action;
|
|
138
|
+
return (_jsxs(AriaToast, { toast: queued, className: ({ isFocusVisible }) => cn("flex min-w-0 items-start gap-2.5 rounded-panel border border-line bg-surface px-3.5 py-3 shadow-float outline-offset-2", focusRing({ isFocusVisible })), children: [_jsx(Icon, { className: `mt-px size-(--size-icon-md) shrink-0 ${TONE[tone]}`, strokeWidth: 2, "aria-hidden": true }), _jsxs(ToastContent, { className: "flex min-w-0 flex-1 flex-col gap-1", children: [_jsx(Text, { slot: "title", className: "text-meta text-ink", children: toast.message }), toast.detail === undefined ? null : (_jsx(Text, { slot: "description", className: "text-micro text-muted", children: toast.detail }))] }), action === undefined ? null : action.href !== undefined ? (_jsx(AriaLink, { href: action.href, onPress: close, className: ({ isFocusVisible }) => cn(LINK, "mt-px", focusRing({ isFocusVisible })), children: action.label })) : (_jsx(AriaButton, { onPress: () => {
|
|
139
|
+
action.onSelect?.();
|
|
140
|
+
close();
|
|
141
|
+
}, className: ({ isFocusVisible }) => cn(LINK, "mt-px", focusRing({ isFocusVisible })), children: action.label })), _jsx(AriaButton, { slot: "close", "aria-label": copy.shell.closeNotice, className: ({ isFocusVisible }) => cn("-mt-0.5 -mr-1 inline-flex size-6 shrink-0 cursor-default items-center justify-center rounded-pill text-muted transition-colors duration-(--duration-instant) hover:bg-ink/6 hover:text-ink pressed:bg-ink/10", focusRing({ isFocusVisible })), children: _jsx(X, { className: "size-(--size-icon-sm)", strokeWidth: 2, "aria-hidden": true }) })] }));
|
|
142
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { Callout, type CalloutProps } from "./Callout.js";
|
|
2
|
+
export { Flag, type FlagProps, type FlagTone } from "./Flag.js";
|
|
3
|
+
export { Empty, Failed, ICONO_ROTO, Missing, Skeleton, } from "./Notice.js";
|
|
4
|
+
export { ToastLayer, ToastProvider, type ToastSpec, useToast, useToastIfAny, } from "./Toast.js";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/* How a view says that there is nothing, that it failed, or that nobody knows
|
|
2
|
+
how to draw it. */
|
|
3
|
+
export { Callout } from "./Callout.js";
|
|
4
|
+
export { Flag } from "./Flag.js";
|
|
5
|
+
export { Empty, Failed, ICONO_ROTO, Missing, Skeleton, } from "./Notice.js";
|
|
6
|
+
export { ToastLayer, ToastProvider, useToast, useToastIfAny, } from "./Toast.js";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { type PopoverProps as AriaPopoverProps } from "react-aria-components";
|
|
3
|
+
export type PopoverProps = Omit<AriaPopoverProps, "children"> & {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
};
|
|
6
|
+
export declare function Popover({ children, className, offset, ...props }: PopoverProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { tv } from "../../foundations/variants.js";
|
|
4
|
+
import { composeRenderProps, Popover as AriaPopover, } from "react-aria-components";
|
|
5
|
+
/**
|
|
6
|
+
* What floats over the page anchored to something: the calendar of a date picker, the list
|
|
7
|
+
* of a combobox, a small form opened from a button.
|
|
8
|
+
*
|
|
9
|
+
* Context: it is the same surface the system's `Menu` floats on — raised white, strong
|
|
10
|
+
* hairline, the `float` shadow — so everything that opens on top of the page reads as one
|
|
11
|
+
* kind of thing. React Aria places it, flips it when it does not fit, traps and returns the
|
|
12
|
+
* focus and closes it on Escape; this only dresses it.
|
|
13
|
+
*/
|
|
14
|
+
const popover = tv({
|
|
15
|
+
base: "rounded-panel border border-line-strong bg-surface text-ink shadow-float outline-0 transition-[opacity,translate] duration-(--duration-quick) ease-(--ease-out) forced-colors:bg-[Canvas]",
|
|
16
|
+
variants: {
|
|
17
|
+
isEntering: {
|
|
18
|
+
true: "opacity-0 placement-bottom:-translate-y-1 placement-top:translate-y-1",
|
|
19
|
+
},
|
|
20
|
+
isExiting: {
|
|
21
|
+
true: "opacity-0",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
export function Popover({ children, className, offset = 6, ...props }) {
|
|
26
|
+
return (_jsx(AriaPopover, { offset: offset, ...props, className: composeRenderProps(className, (own, state) => popover({ ...state, className: own })), children: children }));
|
|
27
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type PopoverProps } from "./Popover.js";
|
|
2
|
+
import { type ReactElement, type ReactNode } from "react";
|
|
3
|
+
/** What the trigger has to accept: the system's `Button` and `IconButton` both do — they
|
|
4
|
+
* forward `ref`, spread native props and say what they open. */
|
|
5
|
+
type TriggerElement = ReactElement<{
|
|
6
|
+
expanded?: boolean;
|
|
7
|
+
haspopup?: "menu" | "dialog" | "listbox";
|
|
8
|
+
}>;
|
|
9
|
+
type Naming =
|
|
10
|
+
/** Drawn at the top of the popover, and its accessible name. */
|
|
11
|
+
{
|
|
12
|
+
title: string;
|
|
13
|
+
label?: undefined;
|
|
14
|
+
}
|
|
15
|
+
/** The accessible name when the popover has no visible title — a detail card whose
|
|
16
|
+
* first line already says what it is. */
|
|
17
|
+
| {
|
|
18
|
+
title?: undefined;
|
|
19
|
+
label: string;
|
|
20
|
+
};
|
|
21
|
+
export type PopoverTriggerProps = Naming & {
|
|
22
|
+
/** The button that opens it — a system `Button` or `IconButton`, written as an element. */
|
|
23
|
+
trigger: TriggerElement;
|
|
24
|
+
/** The content. As a function it receives `close`, for the «Aplicar» of a small form. */
|
|
25
|
+
children: ReactNode | ((close: () => void) => ReactNode);
|
|
26
|
+
placement?: PopoverProps["placement"];
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* A small form or a detail opened from a button, floating next to it: «filtrar por monto»,
|
|
30
|
+
* «ver detalle del cliente».
|
|
31
|
+
*
|
|
32
|
+
* Context: it is the step below `Dialog`. A dialog takes the screen and asks something
|
|
33
|
+
* before moving on; this asks two numbers WITHOUT hiding the table they filter — the
|
|
34
|
+
* person keeps seeing what the answer is about. It floats on the system's `Popover`, so it
|
|
35
|
+
* looks like the calendar of a date picker and the list of a combobox.
|
|
36
|
+
*
|
|
37
|
+
* ── Why `trigger={<Button …/>}` and not a compound `<PopoverTrigger.Button>` ──
|
|
38
|
+
*
|
|
39
|
+
* The opener is an ordinary system button, written the way every other button of the
|
|
40
|
+
* screen is written. A compound form would need a second button component — a React Aria
|
|
41
|
+
* `Button` dressed like ours — and two buttons that look the same and are different
|
|
42
|
+
* components drift apart by the third release. One prop, one element, one button.
|
|
43
|
+
*
|
|
44
|
+
* ── It is React Aria's `DialogTrigger`, through `Pressable` ──
|
|
45
|
+
*
|
|
46
|
+
* `DialogTrigger` hands its trigger the press handlers and a `ref` through context; React
|
|
47
|
+
* Aria's `Pressable` is the adapter that clones them onto any element that forwards `ref`
|
|
48
|
+
* and spreads native props — which the system's buttons do. So the popover is anchored to
|
|
49
|
+
* the button itself, focus goes into the dialog and comes back to the button, and Escape
|
|
50
|
+
* and a click outside close it. The open state is kept here only to tell the button
|
|
51
|
+
* `expanded` and `haspopup="dialog"`, so it says what it opens the same way a menu
|
|
52
|
+
* trigger says it.
|
|
53
|
+
*/
|
|
54
|
+
export declare function PopoverTrigger({ trigger, title, label, children, placement, }: PopoverTriggerProps): import("react").JSX.Element;
|
|
55
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Popover } from "./Popover.js";
|
|
4
|
+
import { cloneElement, useState, } from "react";
|
|
5
|
+
import { Dialog, DialogTrigger, Heading, Pressable } from "react-aria-components";
|
|
6
|
+
/**
|
|
7
|
+
* A small form or a detail opened from a button, floating next to it: «filtrar por monto»,
|
|
8
|
+
* «ver detalle del cliente».
|
|
9
|
+
*
|
|
10
|
+
* Context: it is the step below `Dialog`. A dialog takes the screen and asks something
|
|
11
|
+
* before moving on; this asks two numbers WITHOUT hiding the table they filter — the
|
|
12
|
+
* person keeps seeing what the answer is about. It floats on the system's `Popover`, so it
|
|
13
|
+
* looks like the calendar of a date picker and the list of a combobox.
|
|
14
|
+
*
|
|
15
|
+
* ── Why `trigger={<Button …/>}` and not a compound `<PopoverTrigger.Button>` ──
|
|
16
|
+
*
|
|
17
|
+
* The opener is an ordinary system button, written the way every other button of the
|
|
18
|
+
* screen is written. A compound form would need a second button component — a React Aria
|
|
19
|
+
* `Button` dressed like ours — and two buttons that look the same and are different
|
|
20
|
+
* components drift apart by the third release. One prop, one element, one button.
|
|
21
|
+
*
|
|
22
|
+
* ── It is React Aria's `DialogTrigger`, through `Pressable` ──
|
|
23
|
+
*
|
|
24
|
+
* `DialogTrigger` hands its trigger the press handlers and a `ref` through context; React
|
|
25
|
+
* Aria's `Pressable` is the adapter that clones them onto any element that forwards `ref`
|
|
26
|
+
* and spreads native props — which the system's buttons do. So the popover is anchored to
|
|
27
|
+
* the button itself, focus goes into the dialog and comes back to the button, and Escape
|
|
28
|
+
* and a click outside close it. The open state is kept here only to tell the button
|
|
29
|
+
* `expanded` and `haspopup="dialog"`, so it says what it opens the same way a menu
|
|
30
|
+
* trigger says it.
|
|
31
|
+
*/
|
|
32
|
+
export function PopoverTrigger({ trigger, title, label, children, placement = "bottom start", }) {
|
|
33
|
+
const [open, setOpen] = useState(false);
|
|
34
|
+
return (_jsxs(DialogTrigger, { isOpen: open, onOpenChange: setOpen, children: [_jsx(Pressable, { children:
|
|
35
|
+
/* React Aria types the child as an intrinsic element; a component that forwards
|
|
36
|
+
`ref` and spreads native props is the same thing to it at runtime. */
|
|
37
|
+
cloneElement(trigger, { expanded: open, haspopup: "dialog" }) }), _jsx(Popover, { placement: placement, children: _jsx(Dialog, { "aria-label": label, className: "max-h-[inherit] max-w-sm min-w-56 overflow-auto p-3 outline-0", children: ({ close }) => (_jsxs(_Fragment, { children: [title === undefined ? null : (_jsx(Heading, { slot: "title", className: "mb-2 text-meta font-semibold text-ink", children: title })), typeof children === "function" ? children(close) : children] })) }) })] }));
|
|
38
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type PopoverProps } from "./Popover.js";
|
|
2
|
+
import { type ReactElement, type ReactNode } from "react";
|
|
3
|
+
export type PreviewProps = {
|
|
4
|
+
/** What the preview is of — a link to the record, written as an element: a React Aria
|
|
5
|
+
* `Link`, or anything that forwards `ref` and spreads native props. It must be
|
|
6
|
+
* focusable: the keyboard reaches the preview through it. */
|
|
7
|
+
trigger: ReactElement;
|
|
8
|
+
/** The accessible name of the floating card: «Resumen de Distribuidora Andina». */
|
|
9
|
+
label: string;
|
|
10
|
+
/** What floats: a `RelationCard`, a few facts. It may hold actions — «Abrir». */
|
|
11
|
+
children: ReactNode;
|
|
12
|
+
placement?: PopoverProps["placement"];
|
|
13
|
+
/** Milliseconds resting on the trigger before it opens. 600 by default — see above. */
|
|
14
|
+
delay?: number;
|
|
15
|
+
closeDelay?: number;
|
|
16
|
+
/** The trigger stays, the preview does not open. */
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
};
|
|
19
|
+
export declare function Preview({ trigger, label, children, placement, delay, closeDelay, disabled, }: PreviewProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Popover } from "./Popover.js";
|
|
4
|
+
import { isValidElement } from "react";
|
|
5
|
+
import { Focusable, Link, PreviewTrigger } from "react-aria-components";
|
|
6
|
+
/**
|
|
7
|
+
* A glance at a record without leaving the screen: the customer of an invoice, the product
|
|
8
|
+
* of a line — who or what it is, shown when the pointer rests on its name, the keyboard
|
|
9
|
+
* stops on it or a finger presses it long.
|
|
10
|
+
*
|
|
11
|
+
* Context: it sits between the two other things that float from a mark.
|
|
12
|
+
*
|
|
13
|
+
* - `Tooltip` is a LABEL: a few words about a control, never interactive — React Aria makes
|
|
14
|
+
* it a `role="tooltip"` the keyboard cannot enter. A customer's debt, phone and «Abrir»
|
|
15
|
+
* are not a label.
|
|
16
|
+
* - `PopoverTrigger` is ASKED FOR: a press on a button opens a dialog that takes the focus
|
|
17
|
+
* until it is closed. Right for a small form; too much ceremony to see whose NIT it is.
|
|
18
|
+
*
|
|
19
|
+
* `Preview` is React Aria's `PreviewTrigger`: a NON-modal popover (`role="dialog"`, named by
|
|
20
|
+
* `label`) that opens on hover, on keyboard focus and on long press, and that MAY hold
|
|
21
|
+
* interactive content. It does not take the focus when it opens — the person who tabs past
|
|
22
|
+
* a column of customers keeps walking the table — but Tab from the trigger goes into it,
|
|
23
|
+
* Escape closes it and returns to the trigger, and the pointer can travel diagonally from the
|
|
24
|
+
* name to the card without it closing (React Aria's safe area). A long press moves the focus
|
|
25
|
+
* into it, so a touch screen reader lands on the card. All of that is React Aria's; this
|
|
26
|
+
* only floats it on the system's `Popover` — the surface, hairline, radius and `float`
|
|
27
|
+
* shadow everything that opens over the page has.
|
|
28
|
+
*
|
|
29
|
+
* ── The delays: 600 ms to open, 200 ms to close ──────────────────────
|
|
30
|
+
*
|
|
31
|
+
* They are React Aria's defaults, written here so a change in the library does not change
|
|
32
|
+
* the system silently. 600 ms is the line between crossing and resting: in a table the
|
|
33
|
+
* pointer crosses customer names all day on its way to something else, and a card of 18rem
|
|
34
|
+
* that covered four rows on every pass would be noise. Below ~400 ms it opens on the way;
|
|
35
|
+
* above a second it feels broken to the person who did stop to look. Keyboard focus waits the
|
|
36
|
+
* same, so tabbing quickly does not open a card per row. Once one is open the next opens at
|
|
37
|
+
* once (React Aria's warm-up): comparing the customers of six invoices is a run down the
|
|
38
|
+
* column, not six waits. It is shorter than a tooltip's on purpose — a preview is asked for
|
|
39
|
+
* by resting on a NAME you want to know about; a tooltip names an icon you mostly know.
|
|
40
|
+
* 200 ms to close is only for the jittery exit; the trip to the card is covered by the
|
|
41
|
+
* safe area, not by the delay.
|
|
42
|
+
*
|
|
43
|
+
* ── The trigger ──────────────────────────────────────────────────────
|
|
44
|
+
*
|
|
45
|
+
* React Aria hands the trigger its hover, focus, long-press and `aria-*` props through
|
|
46
|
+
* context. A React Aria `Link` — the name of a `RelationValue` — reads that context itself,
|
|
47
|
+
* so it is passed as is. Anything else — a system `Button`, a `ButtonLink`, a plain `<a>` —
|
|
48
|
+
* goes through `Focusable`, the adapter that clones those props and a `ref` onto an element
|
|
49
|
+
* that forwards `ref` and spreads native props (the same one `TooltipTrigger` uses). Wrapping
|
|
50
|
+
* the `Link` too would hand it the props twice.
|
|
51
|
+
*/
|
|
52
|
+
const PREVIEW_DELAY = 600;
|
|
53
|
+
const PREVIEW_CLOSE_DELAY = 200;
|
|
54
|
+
export function Preview({ trigger, label, children, placement = "bottom start", delay = PREVIEW_DELAY, closeDelay = PREVIEW_CLOSE_DELAY, disabled = false, }) {
|
|
55
|
+
const readsContext = isValidElement(trigger) && trigger.type === Link;
|
|
56
|
+
return (_jsxs(PreviewTrigger, { delay: delay, closeDelay: closeDelay, isDisabled: disabled, children: [readsContext ? (trigger) : (
|
|
57
|
+
/* React Aria types the child as an intrinsic element; a component that forwards
|
|
58
|
+
`ref` and spreads native props is the same thing to it at runtime. */
|
|
59
|
+
_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
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { type TooltipProps as AriaTooltipProps } from "react-aria-components";
|
|
3
|
+
export type TooltipProps = Omit<AriaTooltipProps, "children" | "offset"> & {
|
|
4
|
+
/** A few words. A sentence of help is a `Hint`, not a tooltip. */
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
/** The small point toward the control. On by default: in a row of five icons it says
|
|
7
|
+
* WHICH one is being named. */
|
|
8
|
+
arrow?: boolean;
|
|
9
|
+
};
|
|
10
|
+
export declare function Tooltip({ children, arrow, className, ...props }: TooltipProps): import("react").JSX.Element;
|
|
11
|
+
export declare function useInTooltipTrigger(): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Ties a control to its `Tooltip`: the control first, the tooltip second.
|
|
14
|
+
*
|
|
15
|
+
* Context: the timing is React Aria's on purpose — its default delay before the first one,
|
|
16
|
+
* and the warm-up that opens the next one at once while the pointer runs along a row of
|
|
17
|
+
* icons. A tooltip that opens on every pass of the mouse is noise; one that makes you wait
|
|
18
|
+
* on each icon of a row is a toll. `delay` is here for the rare control that needs it, not
|
|
19
|
+
* to tune each screen.
|
|
20
|
+
*
|
|
21
|
+
* ── Why the control is wrapped in `Focusable` here ──
|
|
22
|
+
*
|
|
23
|
+
* React Aria's trigger hands its pointer, focus and `aria-describedby` props to the control
|
|
24
|
+
* through context, and only a React Aria component reads that context. `Focusable` is the
|
|
25
|
+
* adapter for everything else: it clones the control with those props and a `ref`. Doing it
|
|
26
|
+
* here means a consumer writes `<TooltipTrigger><Button …/><Tooltip …/></TooltipTrigger>`
|
|
27
|
+
* with the system's own buttons — they forward `ref` and spread native props — or with a
|
|
28
|
+
* plain `<button>`, and never has to know the adapter exists.
|
|
29
|
+
*/
|
|
30
|
+
export declare function TooltipTrigger({ children, delay, closeDelay, disabled, }: {
|
|
31
|
+
/** The control — one element that forwards `ref` and spreads native props, like every
|
|
32
|
+
* system button — then the `Tooltip`. */
|
|
33
|
+
children: ReactNode;
|
|
34
|
+
/** Milliseconds before the first tooltip opens. React Aria's default when absent. */
|
|
35
|
+
delay?: number;
|
|
36
|
+
closeDelay?: number;
|
|
37
|
+
disabled?: boolean;
|
|
38
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { tv } from "../../foundations/variants.js";
|
|
4
|
+
import { Children, createContext, isValidElement, useContext, } from "react";
|
|
5
|
+
import { composeRenderProps, Focusable, OverlayArrow, Tooltip as AriaTooltip, TooltipTrigger as AriaTooltipTrigger, } from "react-aria-components";
|
|
6
|
+
/**
|
|
7
|
+
* The name of a control that has no visible name — an icon button of a table row, of a
|
|
8
|
+
* toolbar — shown when the pointer rests on it or the keyboard reaches it.
|
|
9
|
+
*
|
|
10
|
+
* Context: it is not the other two ways of putting words next to a mark.
|
|
11
|
+
*
|
|
12
|
+
* - `Hint` is CONTENT: a sentence of help that does not fit on the page, behind a `?`. It
|
|
13
|
+
* opens on press and stays, because it is meant to be read, on a tablet too.
|
|
14
|
+
* - The native `title` is the browser's tooltip: it shows late, unstyled, never on keyboard
|
|
15
|
+
* focus and never on touch — the person who navigates with Tab never learns what the
|
|
16
|
+
* trash can is. That is why icon targets no longer use it.
|
|
17
|
+
*
|
|
18
|
+
* `Tooltip` is the LABEL, shown: a few words, on hover AND on focus, never interactive, never
|
|
19
|
+
* pinned. It is drawn on the chrome (the dark surface of the shell) because it is not part
|
|
20
|
+
* of the page — it is a whisper about one control, and the dark box reads as that against
|
|
21
|
+
* a page of white surfaces. React Aria decides when: its default delay on the first hover,
|
|
22
|
+
* then the next tooltip of the row opens at once (warm-up), and Escape closes it.
|
|
23
|
+
*
|
|
24
|
+
* Every `IconButton` and `IconLink` already draws one with its `label`; write a
|
|
25
|
+
* `TooltipTrigger` by hand only for a control that is not an icon target, or to say more
|
|
26
|
+
* than the label.
|
|
27
|
+
*/
|
|
28
|
+
const tooltip = tv({
|
|
29
|
+
base: "group max-w-64 rounded-control bg-chrome px-2 py-1 text-micro text-chrome-ink shadow-float transition-[opacity,translate] duration-(--duration-quick) ease-(--ease-out) forced-colors:border forced-colors:border-[ButtonBorder] forced-colors:bg-[Canvas] forced-colors:text-[CanvasText]",
|
|
30
|
+
variants: {
|
|
31
|
+
isEntering: {
|
|
32
|
+
true: "opacity-0 placement-top:translate-y-1 placement-bottom:-translate-y-1 placement-left:translate-x-1 placement-right:-translate-x-1",
|
|
33
|
+
},
|
|
34
|
+
isExiting: {
|
|
35
|
+
true: "opacity-0",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
export function Tooltip({ children, arrow = true, className, ...props }) {
|
|
40
|
+
return (_jsxs(AriaTooltip, { offset: arrow ? 8 : 4, ...props, className: composeRenderProps(className, (own, state) => tooltip({ ...state, className: own })), children: [arrow ? (_jsx(OverlayArrow, { children: _jsx("svg", { viewBox: "0 0 8 8", "aria-hidden": true, className: "block size-2 fill-chrome group-placement-bottom:rotate-180 group-placement-left:-rotate-90 group-placement-right:rotate-90 forced-colors:fill-[Canvas]", children: _jsx("path", { d: "M0 0 L4 4 L8 0" }) }) })) : null, children] }));
|
|
41
|
+
}
|
|
42
|
+
/** True inside the control slot of a `TooltipTrigger`: an `IconButton` written there leaves
|
|
43
|
+
* its own tooltip out, so the consumer's is the only one. */
|
|
44
|
+
const InTooltipTrigger = createContext(false);
|
|
45
|
+
export function useInTooltipTrigger() {
|
|
46
|
+
return useContext(InTooltipTrigger);
|
|
47
|
+
}
|
|
48
|
+
function isDisabledControl(control) {
|
|
49
|
+
if (!isValidElement(control))
|
|
50
|
+
return false;
|
|
51
|
+
return control.props.disabled === true || control.props.isDisabled === true;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Ties a control to its `Tooltip`: the control first, the tooltip second.
|
|
55
|
+
*
|
|
56
|
+
* Context: the timing is React Aria's on purpose — its default delay before the first one,
|
|
57
|
+
* and the warm-up that opens the next one at once while the pointer runs along a row of
|
|
58
|
+
* icons. A tooltip that opens on every pass of the mouse is noise; one that makes you wait
|
|
59
|
+
* on each icon of a row is a toll. `delay` is here for the rare control that needs it, not
|
|
60
|
+
* to tune each screen.
|
|
61
|
+
*
|
|
62
|
+
* ── Why the control is wrapped in `Focusable` here ──
|
|
63
|
+
*
|
|
64
|
+
* React Aria's trigger hands its pointer, focus and `aria-describedby` props to the control
|
|
65
|
+
* through context, and only a React Aria component reads that context. `Focusable` is the
|
|
66
|
+
* adapter for everything else: it clones the control with those props and a `ref`. Doing it
|
|
67
|
+
* here means a consumer writes `<TooltipTrigger><Button …/><Tooltip …/></TooltipTrigger>`
|
|
68
|
+
* with the system's own buttons — they forward `ref` and spread native props — or with a
|
|
69
|
+
* plain `<button>`, and never has to know the adapter exists.
|
|
70
|
+
*/
|
|
71
|
+
export function TooltipTrigger({ children, delay, closeDelay, disabled = false, }) {
|
|
72
|
+
const [control, ...tooltip] = Children.toArray(children);
|
|
73
|
+
return (_jsxs(AriaTooltipTrigger, { delay: delay, closeDelay: closeDelay, isDisabled: disabled, children: [_jsx(InTooltipTrigger.Provider, { value: true, children: _jsx(Focusable, { isDisabled: isDisabledControl(control), children: control }) }), tooltip] }));
|
|
74
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { Popover, type PopoverProps } from "./Popover.js";
|
|
2
|
+
export { PopoverTrigger, type PopoverTriggerProps, } from "./PopoverTrigger.js";
|
|
3
|
+
export { Preview, type PreviewProps } from "./Preview.js";
|
|
4
|
+
export { Tooltip, type TooltipProps, TooltipTrigger, } from "./Tooltip.js";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/* LO QUE FLOTA anclado a algo: el calendario de un selector de fecha, la lista de un
|
|
2
|
+
combobox, un formulario chico abierto desde un botón, el nombre de un ícono, el vistazo
|
|
3
|
+
a un registro al pasar por su nombre. */
|
|
4
|
+
export { Popover } from "./Popover.js";
|
|
5
|
+
export { PopoverTrigger, } from "./PopoverTrigger.js";
|
|
6
|
+
export { Preview } from "./Preview.js";
|
|
7
|
+
export { Tooltip, TooltipTrigger, } from "./Tooltip.js";
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { type FormMode } from "../field/mode.js";
|
|
2
|
+
import type { LucideIcon } from "lucide-react";
|
|
3
|
+
import { type ReactNode } from "react";
|
|
4
|
+
export interface PickerItem {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
/** The data that allows choosing well, already formatted: stock on hand, a
|
|
8
|
+
* price, a tax id. In tabular figures, so they line up between options, which
|
|
9
|
+
* is what makes them comparable at a glance. */
|
|
10
|
+
meta?: string;
|
|
11
|
+
tags?: ReactNode;
|
|
12
|
+
icon?: LucideIcon;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
/** Extra text WHERE TO SEARCH that is not drawn: the internal code and the
|
|
15
|
+
* barcode of a product, the NIT of a customer.
|
|
16
|
+
*
|
|
17
|
+
* Context: it is the difference between being able to type the code printed
|
|
18
|
+
* on the box and finding the product, and having to remember what it was
|
|
19
|
+
* called. Drawing every code of every option is not an alternative. */
|
|
20
|
+
haystack?: string;
|
|
21
|
+
/** The header this item is listed under. Consecutive items with the same section share
|
|
22
|
+
* one header; an item without it is listed loose, outside any section. */
|
|
23
|
+
section?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* What the list shows and how many are left out of it. Capped at 8 with the footer count,
|
|
27
|
+
* unless the list loads incrementally — then everything that arrived is listed and the
|
|
28
|
+
* scroll asks for more.
|
|
29
|
+
*/
|
|
30
|
+
export declare function shownRows(filtered: readonly PickerItem[], { loading, incremental }: {
|
|
31
|
+
loading: boolean;
|
|
32
|
+
incremental: boolean;
|
|
33
|
+
}): {
|
|
34
|
+
visible: readonly PickerItem[];
|
|
35
|
+
remaining: number;
|
|
36
|
+
};
|
|
37
|
+
export type PickerProps = {
|
|
38
|
+
id?: string;
|
|
39
|
+
items: readonly PickerItem[];
|
|
40
|
+
value?: string;
|
|
41
|
+
onChange?: (id: string) => void;
|
|
42
|
+
placeholder?: string;
|
|
43
|
+
/** What to do with the ones that don't fit. Without this, the footer only
|
|
44
|
+
* informs. */
|
|
45
|
+
onBrowseAll?: () => void;
|
|
46
|
+
/** Open the chosen record. Drawn as a trailing affordance, the way a many2one
|
|
47
|
+
* offers "open" next to "pick". Absent when there is nothing chosen. */
|
|
48
|
+
onOpen?: (id: string) => void;
|
|
49
|
+
emptyText?: string;
|
|
50
|
+
/** Placeholder of the field while it is being typed into. It is NOT a second
|
|
51
|
+
* box — the field itself is the search. */
|
|
52
|
+
searchPlaceholder?: string;
|
|
53
|
+
/** The options are still coming. It gets said, not hidden: an empty picker
|
|
54
|
+
* while loading reads as "there is nothing", which is a different answer. */
|
|
55
|
+
loading?: boolean;
|
|
56
|
+
loadingText?: string;
|
|
57
|
+
/** The list grows from the backend: called when its end scrolls into view. The consumer
|
|
58
|
+
* appends the next page to `items`; with nothing more to fetch it does nothing. Turns off
|
|
59
|
+
* the cap of 8, the «see all…» row and the footer count. */
|
|
60
|
+
onLoadMore?: () => void;
|
|
61
|
+
/** A page asked for by `onLoadMore` is on its way: a quiet row at the end says so. Unlike
|
|
62
|
+
* `loading`, the rows already there stay. */
|
|
63
|
+
loadingMore?: boolean;
|
|
64
|
+
describedBy?: string;
|
|
65
|
+
invalid?: boolean;
|
|
66
|
+
disabled?: boolean;
|
|
67
|
+
/** The name of the control when it is NOT inside a `Field` — a cell of an editable table.
|
|
68
|
+
* Inside a field, the field's label names it. */
|
|
69
|
+
"aria-label"?: string;
|
|
70
|
+
/** Forces the mode. By default it inherits the form's: in read mode it draws the name. */
|
|
71
|
+
mode?: FormMode;
|
|
72
|
+
};
|
|
73
|
+
export declare function Picker({ mode, ...props }: PickerProps): import("react").JSX.Element;
|
|
74
|
+
/**
|
|
75
|
+
* The list of a `Picker`: its rows, the section headers, the «see all…» row and the loading
|
|
76
|
+
* row at the end. Internal — not exported from the family — and apart from the combobox so it
|
|
77
|
+
* can be rendered on the server without a popover, which is how the tests see the rows.
|
|
78
|
+
*/
|
|
79
|
+
export declare function PickerOptions({ items, marks, hints, remaining, emptyText, onLoadMore, loadingMore, "aria-label": ariaLabel, }: {
|
|
80
|
+
items: readonly PickerItem[];
|
|
81
|
+
marks: boolean;
|
|
82
|
+
hints: boolean;
|
|
83
|
+
remaining: number;
|
|
84
|
+
emptyText: string;
|
|
85
|
+
onLoadMore?: () => void;
|
|
86
|
+
loadingMore?: boolean;
|
|
87
|
+
/** Only when rendered outside the combobox, which otherwise names the list. */
|
|
88
|
+
"aria-label"?: string;
|
|
89
|
+
}): import("react").JSX.Element;
|