@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,128 @@
|
|
|
1
|
+
import { type ButtonRole, type ButtonSize } from "../button/Button.js";
|
|
2
|
+
import type { LucideIcon } from "lucide-react";
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import { type Key } from "react-aria-components";
|
|
5
|
+
export interface MenuOption {
|
|
6
|
+
id: string;
|
|
7
|
+
label: string;
|
|
8
|
+
/** The line below. It exists because a thirty-entry menu without hints forces
|
|
9
|
+
* you to open each one to discover what it does. */
|
|
10
|
+
hint?: string;
|
|
11
|
+
icon?: LucideIcon;
|
|
12
|
+
/** For the menus that CHOOSE (group by, favorites) instead of executing. */
|
|
13
|
+
checked?: boolean;
|
|
14
|
+
/** A hairline ABOVE this option. It separates the three rows of a date —months,
|
|
15
|
+
* quarters, years— which are one group because they combine, but read as
|
|
16
|
+
* three because they answer at three different zooms. */
|
|
17
|
+
divider?: boolean;
|
|
18
|
+
/** Cómo se SACA esta opción cuando se dibuja como ficha, si sacarla no es lo mismo que
|
|
19
|
+
* volver a elegirla. El filtro a medida es el caso: elegirlo abre el constructor, y eso
|
|
20
|
+
* no es lo que tiene que pasar al tocar su ×. */
|
|
21
|
+
onRemove?: () => void;
|
|
22
|
+
disabled?: boolean;
|
|
23
|
+
/** Visual mark at the end of the label. A string is drawn as a pill
|
|
24
|
+
* —"maqueta", "nuevo"—; anything else is drawn as is, which is how a status
|
|
25
|
+
* `Tag` or an already formatted counter comes in. */
|
|
26
|
+
badge?: ReactNode;
|
|
27
|
+
/** What THIS option does. It exists on top of the menu's `onSelect` because a
|
|
28
|
+
* menu built from a registry —twenty-five operations, each one with its own
|
|
29
|
+
* action— has no reasonable `switch` on the other side. */
|
|
30
|
+
onSelect?: () => void;
|
|
31
|
+
/** The keys that run this option without opening the menu, as the user reads them
|
|
32
|
+
* —«Ctrl+P», «⌘D»—. Drawn at the end of the row and linked to it for a screen reader.
|
|
33
|
+
* It is a HINT: binding the keys is the application's job, because the menu does not
|
|
34
|
+
* exist until it is opened. In an ERP the frequent verbs have a shortcut, and the menu
|
|
35
|
+
* is where people learn it. */
|
|
36
|
+
shortcut?: string;
|
|
37
|
+
/** The option opens a SUBMENU with these options instead of running. Its own `onSelect`
|
|
38
|
+
* and `checked` are ignored — a row that opens something does not also do something.
|
|
39
|
+
* Children may carry `items` too, but a third level is usually a dialog in disguise. */
|
|
40
|
+
items?: readonly MenuOption[];
|
|
41
|
+
}
|
|
42
|
+
export interface MenuGroup {
|
|
43
|
+
/** The group's label, in the brand voice. Without a label, the group is just
|
|
44
|
+
* a separator — which also serves a purpose. */
|
|
45
|
+
label?: string;
|
|
46
|
+
/**
|
|
47
|
+
* The group folds under its label, and starts folded unless something in it
|
|
48
|
+
* is ticked.
|
|
49
|
+
*
|
|
50
|
+
* It exists for the DATE narrowings. «Fecha de pedido» is not one option, it
|
|
51
|
+
* is eleven —three months, four quarters, three years— and eleven options
|
|
52
|
+
* always open push the rest of the panel off the screen. A date is the only
|
|
53
|
+
* field that brings its own little calendar, so it is the only one that
|
|
54
|
+
* folds; the others are a list and a list does not need a door.
|
|
55
|
+
*/
|
|
56
|
+
collapsible?: boolean;
|
|
57
|
+
options: MenuOption[];
|
|
58
|
+
}
|
|
59
|
+
declare const WIDTH: {
|
|
60
|
+
/** A menu of short verbs hanging off an icon: "duplicate", "delete". */
|
|
61
|
+
readonly sm: "min-w-44 max-w-64";
|
|
62
|
+
readonly md: "min-w-56 max-w-80";
|
|
63
|
+
readonly lg: "min-w-80 max-w-(--container-short)";
|
|
64
|
+
};
|
|
65
|
+
/** Every option of the tree by id, children included: a submenu is a separate React Aria
|
|
66
|
+
* menu with its own `onAction`, and both have to reach the same handlers. */
|
|
67
|
+
export declare function indexOptions(options: readonly MenuOption[], into?: Map<string, MenuOption>): Map<string, MenuOption>;
|
|
68
|
+
export declare function Menu({ label, groups, onSelect, surface, align, icon: Trigger,
|
|
69
|
+
/** The menu as a control of a list ("filters ▾") carries the chevron; the
|
|
70
|
+
* actions `⋯` does not, because the icon already says there is more. */
|
|
71
|
+
chevron, active, iconOnly, variant, searchPlaceholder, emptyText, width, role, size, title, closeOnSelect, }: {
|
|
72
|
+
label: string;
|
|
73
|
+
groups: readonly MenuGroup[];
|
|
74
|
+
onSelect?: (id: string) => void;
|
|
75
|
+
surface?: "canvas" | "chrome";
|
|
76
|
+
align?: "start" | "end";
|
|
77
|
+
icon?: LucideIcon;
|
|
78
|
+
chevron?: boolean;
|
|
79
|
+
/** Something is chosen inside. A filter menu that doesn't look different when
|
|
80
|
+
* it filters is how someone ends up looking at a trimmed list without
|
|
81
|
+
* knowing it. */
|
|
82
|
+
active?: boolean;
|
|
83
|
+
/** `text` removes the frame and the background: it is the menu of an
|
|
84
|
+
* application BAR. `button` —the default— is the menu of a control. */
|
|
85
|
+
variant?: "button" | "text";
|
|
86
|
+
/** With a search box, the menu filters over label and hint. It only goes in
|
|
87
|
+
* when there are enough entries for searching to gain something — a search
|
|
88
|
+
* box on top of four options is one more control between the hand and what
|
|
89
|
+
* was already visible. */
|
|
90
|
+
searchPlaceholder?: string;
|
|
91
|
+
emptyText?: string;
|
|
92
|
+
/** `md` for a menu of verbs; `lg` when the entries carry a hint that cannot be
|
|
93
|
+
* truncated without losing its reason to exist. */
|
|
94
|
+
width?: keyof typeof WIDTH;
|
|
95
|
+
/** The trigger looks like a BUTTON of that role. It is for the menu that
|
|
96
|
+
* starts something —"+ Create"—, which has to look like the main action and
|
|
97
|
+
* not like a control of the list. Without this, the trigger is the usual
|
|
98
|
+
* small one. */
|
|
99
|
+
role?: ButtonRole;
|
|
100
|
+
size?: ButtonSize;
|
|
101
|
+
/** What explains the menu on hover. It is not drawn. */
|
|
102
|
+
title?: string;
|
|
103
|
+
/** Icon only. `label` becomes the accessible name and the `title`, and stops
|
|
104
|
+
* being drawn — it is the actions `⋯`, where the icon already says there is
|
|
105
|
+
* more.
|
|
106
|
+
*
|
|
107
|
+
* The label does NOT become optional when hidden: a menu without an
|
|
108
|
+
* accessible name is a menu a screen reader announces as "button". */
|
|
109
|
+
iconOnly?: boolean;
|
|
110
|
+
/** Column and filter menus toggle several options in a row. Closing after
|
|
111
|
+
* each click turns three hides into three open-close round trips. */
|
|
112
|
+
closeOnSelect?: boolean;
|
|
113
|
+
}): import("react").JSX.Element;
|
|
114
|
+
/**
|
|
115
|
+
* The panel of a `Menu`: its groups and rows, and the submenus hanging from them. Internal —
|
|
116
|
+
* it is not exported from the family — and separate from the trigger for two reasons: a
|
|
117
|
+
* submenu is this same panel one level down, and a panel can be rendered on the server
|
|
118
|
+
* without the trigger's popover, which is how the tests see the rows.
|
|
119
|
+
*/
|
|
120
|
+
export declare function MenuList({ label, groups, onAction, closeOnSelect, emptyText, width, }: {
|
|
121
|
+
label: string;
|
|
122
|
+
groups: readonly MenuGroup[];
|
|
123
|
+
onAction: (key: Key) => void;
|
|
124
|
+
closeOnSelect: boolean;
|
|
125
|
+
emptyText: string;
|
|
126
|
+
width?: keyof typeof WIDTH;
|
|
127
|
+
}): import("react").JSX.Element;
|
|
128
|
+
export {};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useUiCopy } from "../../foundations/copy.js";
|
|
4
|
+
import { buttonClasses, } from "../button/Button.js";
|
|
5
|
+
import { Popover } from "../overlay/Popover.js";
|
|
6
|
+
import { Check, ChevronDown, ChevronRight } from "lucide-react";
|
|
7
|
+
import { Autocomplete, Button as AriaButton, Header, Input, Keyboard, Menu as AriaMenu, MenuItem, MenuSection, MenuTrigger, SearchField, Separator, SubmenuTrigger, useFilter, } from "react-aria-components";
|
|
8
|
+
const WIDTH = {
|
|
9
|
+
/** A menu of short verbs hanging off an icon: "duplicate", "delete". */
|
|
10
|
+
sm: "min-w-44 max-w-64",
|
|
11
|
+
md: "min-w-56 max-w-80",
|
|
12
|
+
lg: "min-w-80 max-w-(--container-short)",
|
|
13
|
+
};
|
|
14
|
+
/** A submenu opens beside its row, flush against the parent panel and pulled up by the
|
|
15
|
+
* panel's padding plus its hairline (`p-1` + 1), so its first row lines up with the row that
|
|
16
|
+
* opened it — a submenu that starts half a row lower reads as belonging to the next one. */
|
|
17
|
+
const SUBMENU_OFFSET = 0;
|
|
18
|
+
const SUBMENU_CROSS_OFFSET = -5;
|
|
19
|
+
/** Every option of the tree by id, children included: a submenu is a separate React Aria
|
|
20
|
+
* menu with its own `onAction`, and both have to reach the same handlers. */
|
|
21
|
+
export function indexOptions(options, into = new Map()) {
|
|
22
|
+
for (const o of options) {
|
|
23
|
+
into.set(o.id, o);
|
|
24
|
+
if (o.items !== undefined)
|
|
25
|
+
indexOptions(o.items, into);
|
|
26
|
+
}
|
|
27
|
+
return into;
|
|
28
|
+
}
|
|
29
|
+
export function Menu({ label, groups, onSelect, surface = "canvas", align = "start", icon: Trigger,
|
|
30
|
+
/** The menu as a control of a list ("filters ▾") carries the chevron; the
|
|
31
|
+
* actions `⋯` does not, because the icon already says there is more. */
|
|
32
|
+
chevron = true, active = false, iconOnly = false, variant = "button", searchPlaceholder, emptyText, width = "md", role, size = "md", title, closeOnSelect = true, }) {
|
|
33
|
+
const copy = useUiCopy();
|
|
34
|
+
const { contains } = useFilter({ sensitivity: "base" });
|
|
35
|
+
/* `text` is the menu of a BAR: no frame and no background, just the highlight
|
|
36
|
+
on hover. Five bordered menus on top of an application are five buttons
|
|
37
|
+
competing with the ones in the view, and the bar stops reading as an index.
|
|
38
|
+
`button` is the menu of a CONTROL —"filters ▾", "group ▾"—, and there the
|
|
39
|
+
frame is what says it can be pressed. */
|
|
40
|
+
const triggerClass = (open) => variant === "text"
|
|
41
|
+
? surface === "chrome"
|
|
42
|
+
? `border-transparent text-chrome-muted hover:bg-chrome-ink/12 hover:text-chrome-ink ${active || open ? "bg-chrome-ink/12 text-chrome-ink" : ""}`
|
|
43
|
+
: `border-transparent text-muted hover:bg-ink/6 hover:text-ink ${active || open ? "bg-ink/6 text-ink" : ""}`
|
|
44
|
+
: surface === "chrome"
|
|
45
|
+
? `border-chrome-muted/35 bg-chrome-raised text-chrome-ink hover:border-chrome-accent/45 ${active ? "border-chrome-accent/60" : ""}`
|
|
46
|
+
: `border-line-strong bg-surface hover:border-primary/45 hover:bg-primary/5 ${active ? "border-primary/60 text-primary" : "text-ink"}`;
|
|
47
|
+
const byId = indexOptions(groups.flatMap((g) => g.options));
|
|
48
|
+
const menu = (_jsx(MenuList, { label: label, groups: groups, width: width, closeOnSelect: closeOnSelect, onAction: (key) => {
|
|
49
|
+
const option = byId.get(String(key));
|
|
50
|
+
option?.onSelect?.();
|
|
51
|
+
onSelect?.(String(key));
|
|
52
|
+
}, emptyText: emptyText ?? copy.menu.empty }));
|
|
53
|
+
return (_jsxs(MenuTrigger, { children: [_jsx(AriaButton, { "aria-label": iconOnly ? label : undefined, ...(role === undefined ? {} : { "data-emphasis": role }), className: ({ isPressed, isFocusVisible }) => role === undefined
|
|
54
|
+
? `inline-flex h-(--size-control-sm) shrink-0 cursor-default items-center gap-1.5 border text-micro font-medium whitespace-nowrap outline-0 transition-colors duration-(--duration-instant) ${isFocusVisible ? "outline-2 outline-offset-2 outline-primary" : ""} ${iconOnly ? "w-(--size-control-sm) justify-center rounded-pill" : "rounded-control px-2.5"} ${triggerClass(isPressed)}`
|
|
55
|
+
: `${buttonClasses(role, size, surface)} gap-1.5`, children: _jsxs("span", { className: "contents", title: title ?? (iconOnly ? label : undefined), children: [Trigger === undefined ? (iconOnly ? (_jsx(ChevronDown, { className: "size-(--size-icon-sm)", strokeWidth: 2, "aria-hidden": true })) : null) : (_jsx(Trigger, { className: "size-(--size-icon-sm)", strokeWidth: 2, "aria-hidden": true })), iconOnly ? null : label, chevron && !iconOnly ? (_jsx(ChevronDown, { className: "size-(--size-icon-sm)", strokeWidth: 2, "aria-hidden": true })) : null] }) }), _jsx(Popover, { placement: align === "end" ? "bottom end" : "bottom start", className: WIDTH[width], children: searchPlaceholder === undefined ? (menu) : (
|
|
56
|
+
/* The search box stays at the top: in a menu of twenty-five entries,
|
|
57
|
+
scrolling loses exactly the control that explains why only three are
|
|
58
|
+
visible. It filters on label AND hint — half the entries of this
|
|
59
|
+
product are told apart only by the hint. */
|
|
60
|
+
_jsxs(Autocomplete, { filter: contains, children: [_jsx(SearchField, { "aria-label": searchPlaceholder, autoFocus: true, className: "border-b border-line px-2 pt-2 pb-2", children: _jsx(Input, { placeholder: searchPlaceholder, className: "h-(--size-control-sm) w-full rounded-control border border-line-strong bg-surface px-2.5 text-meta text-ink outline-0 placeholder:text-muted focus-visible:border-primary" }) }), menu] })) })] }));
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The panel of a `Menu`: its groups and rows, and the submenus hanging from them. Internal —
|
|
64
|
+
* it is not exported from the family — and separate from the trigger for two reasons: a
|
|
65
|
+
* submenu is this same panel one level down, and a panel can be rendered on the server
|
|
66
|
+
* without the trigger's popover, which is how the tests see the rows.
|
|
67
|
+
*/
|
|
68
|
+
export function MenuList({ label, groups, onAction, closeOnSelect, emptyText, width = "md", }) {
|
|
69
|
+
/* Per panel: a submenu without icons does not inherit its parent's mark column. */
|
|
70
|
+
const hasMarks = groups.some((g) => g.options.some((o) => o.checked !== undefined || o.icon !== undefined));
|
|
71
|
+
const hasHints = groups.some((g) => g.options.some((o) => o.hint !== undefined));
|
|
72
|
+
const row = (o) => {
|
|
73
|
+
const Icon = o.icon;
|
|
74
|
+
const opens = o.items !== undefined;
|
|
75
|
+
const checked = !opens && o.checked === true;
|
|
76
|
+
return (_jsxs(MenuItem, { id: o.id, textValue: o.hint === undefined ? o.label : `${o.label} ${o.hint}`, isDisabled: o.disabled, className: `flex w-full cursor-default rounded-control ${hasHints ? "items-start py-1.5" : "items-center py-1"} gap-2 px-2 text-left outline-0 transition-colors duration-(--duration-instant) data-focused:bg-primary/8 data-open:bg-primary/8 disabled:opacity-45`, children: [hasMarks ? (_jsx("span", { className: `${hasHints ? "mt-0.5" : ""} flex size-(--size-icon-sm) shrink-0 items-center justify-center`, children: checked ? (_jsx(Check, { className: "size-(--size-icon-sm) text-primary", strokeWidth: 2.5, "aria-hidden": true })) : Icon !== undefined ? (_jsx(Icon, { className: "size-(--size-icon-sm) text-muted", strokeWidth: 2, "aria-hidden": true })) : null })) : null, _jsxs("span", { className: "min-w-0 flex-1", children: [_jsxs("span", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: `truncate text-meta ${checked ? "font-medium text-ink" : "text-ink"}`, children: o.label }), o.badge === undefined ? null : typeof o.badge === "string" ? (_jsx("span", { className: "inline-flex shrink-0 items-center rounded-pill border border-warn/30 bg-warn/10 px-2 py-0.5 text-micro text-warn", children: o.badge })) : (_jsx("span", { className: "ml-auto shrink-0", children: o.badge }))] }), o.hint === undefined ? null : (_jsx("span", { className: "mt-0.5 block truncate text-micro text-muted", children: o.hint }))] }), o.shortcut === undefined ? null : (_jsx(Keyboard, { className: "shrink-0 self-center pl-3 font-mono text-micro text-muted", children: o.shortcut })), opens ? (_jsx(ChevronRight, { className: "size-(--size-icon-sm) shrink-0 self-center text-muted", strokeWidth: 2, "aria-hidden": true })) : null] }, o.id));
|
|
77
|
+
};
|
|
78
|
+
return (_jsx(AriaMenu, { "aria-label": label, shouldCloseOnSelect: closeOnSelect, onAction: onAction, renderEmptyState: () => (_jsx("p", { className: "px-3 py-3 text-meta text-muted italic", children: emptyText })), className: "max-h-[inherit] overflow-y-auto p-1 outline-0", children: groups.map((g, gi) => (_jsxs(MenuSection, { className: "[&:not(:first-child)]:mt-1 [&:not(:first-child)]:border-t [&:not(:first-child)]:border-line [&:not(:first-child)]:pt-1", children: [g.label === undefined ? null : (
|
|
79
|
+
/* Same skeleton as the row: the label sits on the text column. */
|
|
80
|
+
_jsxs(Header, { className: "flex items-center gap-2 px-2 pt-1 pb-0.5 text-micro font-medium text-muted", children: [hasMarks ? (_jsx("span", { className: "size-(--size-icon-sm) shrink-0", "aria-hidden": true })) : null, g.label] })), g.options.flatMap((o) => {
|
|
81
|
+
const item = o.items === undefined ? (row(o)) : (_jsxs(SubmenuTrigger, { children: [row(o), _jsx(Popover, { offset: SUBMENU_OFFSET, crossOffset: SUBMENU_CROSS_OFFSET, className: WIDTH[width], children: _jsx(MenuList, { label: o.label, groups: [{ options: [...o.items] }], onAction: onAction, closeOnSelect: closeOnSelect, emptyText: emptyText, width: width }) })] }, o.id));
|
|
82
|
+
return o.divider === true
|
|
83
|
+
? [
|
|
84
|
+
_jsx(Separator, { className: "my-1 h-px border-0 bg-line" }, `${o.id}-divider`),
|
|
85
|
+
item,
|
|
86
|
+
]
|
|
87
|
+
: [item];
|
|
88
|
+
})] }, g.label ?? gi))) }));
|
|
89
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Menu, type MenuGroup, type MenuOption } from "./Menu.js";
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Tone } from "../../foundations/types.js";
|
|
2
|
+
import type { LucideIcon } from "lucide-react";
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
/**
|
|
5
|
+
* UN AVISO sobre lo que se está mirando.
|
|
6
|
+
*
|
|
7
|
+
* Context: `Empty`, `Failed` y `Missing` dicen que NO HAY contenido y ocupan el
|
|
8
|
+
* lugar de la vista. Esto es lo contrario: hay contenido, se ve, y hace falta
|
|
9
|
+
* decir algo ADEMÁS —«hay asientos sin registrar en este período», «este período
|
|
10
|
+
* está cerrado», «se guardó»—. Sin esta pieza, cada pantalla se lo inventaba
|
|
11
|
+
* con un `div` y un color a ojo, y así es como el mismo grado de urgencia
|
|
12
|
+
* terminaba pintado de tres maneras.
|
|
13
|
+
*
|
|
14
|
+
* ── El tono es la ÚNICA decisión ────────────────────────────────────
|
|
15
|
+
*
|
|
16
|
+
* info un dato que cambia cómo se lee lo de abajo
|
|
17
|
+
* warn algo que conviene mirar antes de seguir
|
|
18
|
+
* bad algo que está mal y ya tuvo consecuencias
|
|
19
|
+
* good algo salió bien y no hay nada que hacer
|
|
20
|
+
*
|
|
21
|
+
* El ícono sale del tono y no se elige: un aviso rojo con un tilde verde adentro
|
|
22
|
+
* es una contradicción que alguien va a escribir si se lo dejamos escribir.
|
|
23
|
+
*
|
|
24
|
+
* ── Lo que NO hace ──────────────────────────────────────────────────
|
|
25
|
+
*
|
|
26
|
+
* No se cierra solo, no tiene temporizador y no se apila. Un aviso que
|
|
27
|
+
* desaparece a los cuatro segundos es un aviso que alguien no llegó a leer — eso
|
|
28
|
+
* es un `Toast`, que es otra pieza y otra pregunta («pasó algo lejos de acá»).
|
|
29
|
+
* Éste habla de lo que está en pantalla, así que se queda mientras sea cierto.
|
|
30
|
+
*/
|
|
31
|
+
export type CalloutProps = {
|
|
32
|
+
/** Qué pasa. Una oración, en la voz del producto. */
|
|
33
|
+
children: ReactNode;
|
|
34
|
+
/** Sin esto, `info`. */
|
|
35
|
+
tone?: Extract<Tone, "info" | "warn" | "bad" | "good">;
|
|
36
|
+
/** El encabezado, cuando la oración sola no alcanza. La mayoría no lo
|
|
37
|
+
* necesita: un aviso de dos renglones con título es un cartel. */
|
|
38
|
+
title?: ReactNode;
|
|
39
|
+
/** Lo que resuelve el aviso, si hay algo que apretar. */
|
|
40
|
+
action?: ReactNode;
|
|
41
|
+
/** Presente = se puede cerrar. Ausente = no, porque lo que dice sigue siendo
|
|
42
|
+
* cierto lo mire quien lo mire. */
|
|
43
|
+
onDismiss?: () => void;
|
|
44
|
+
/** El rótulo accesible de la ×. */
|
|
45
|
+
dismissLabel?: string;
|
|
46
|
+
/** Un ícono propio, cuando el del tono no dice lo suficiente. */
|
|
47
|
+
icon?: LucideIcon;
|
|
48
|
+
};
|
|
49
|
+
export declare function Callout({ children, tone, title, action, onDismiss, dismissLabel, icon, }: CalloutProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { byDensity, useDensity } from "../../foundations/density.js";
|
|
4
|
+
import { AlertTriangle, CheckCircle2, Info, OctagonAlert, X } from "lucide-react";
|
|
5
|
+
const ICON = {
|
|
6
|
+
info: Info,
|
|
7
|
+
warn: AlertTriangle,
|
|
8
|
+
bad: OctagonAlert,
|
|
9
|
+
good: CheckCircle2,
|
|
10
|
+
};
|
|
11
|
+
/* El fondo es una capa del color sobre el papel y no un color propio: así el
|
|
12
|
+
aviso se lee sobre el piso y sobre el papel blanco sin dos juegos de tokens, y
|
|
13
|
+
sigue funcionando el día que cambie la paleta. */
|
|
14
|
+
const SKIN = {
|
|
15
|
+
info: "border-info/25 bg-info/8 text-info",
|
|
16
|
+
warn: "border-warn/30 bg-warn/10 text-warn",
|
|
17
|
+
bad: "border-bad/25 bg-bad/8 text-bad",
|
|
18
|
+
good: "border-good/25 bg-good/8 text-good",
|
|
19
|
+
};
|
|
20
|
+
export function Callout({ children, tone = "info", title, action, onDismiss, dismissLabel, icon, }) {
|
|
21
|
+
const d = useDensity();
|
|
22
|
+
const Icon = icon ?? ICON[tone];
|
|
23
|
+
return (_jsxs("div", {
|
|
24
|
+
/* `status` y no `alert`: `alert` interrumpe al lector de pantalla y se
|
|
25
|
+
reserva para lo que pasó JUSTO AHORA. Un aviso que ya estaba cuando la
|
|
26
|
+
pantalla abrió no interrumpe nada. */
|
|
27
|
+
role: "status", className: `flex min-w-0 items-start gap-2.5 rounded-box border ${SKIN[tone]} ${byDensity(d, "px-3 py-2.5", "px-2.5 py-2")}`, children: [_jsx(Icon, { className: "mt-0.5 size-(--size-icon-sm) shrink-0", strokeWidth: 2, "aria-hidden": true }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-wrap items-start gap-x-3 gap-y-2", children: [_jsxs("div", { className: "flex min-w-0 flex-1 basis-(--container-name) flex-col gap-1", children: [title === undefined ? null : (_jsx("p", { className: "text-meta font-medium text-ink", children: title })), _jsx("div", { className: "min-w-0 text-meta text-ink", children: children })] }), action === undefined ? null : _jsx("span", { className: "shrink-0", children: action })] }), onDismiss === undefined ? null : (_jsx("button", { type: "button", onClick: onDismiss, "aria-label": dismissLabel ?? "cerrar", title: dismissLabel ?? "cerrar", className: "-mr-1 shrink-0 rounded-control p-0.5 text-muted transition-colors duration-(--duration-instant) hover:bg-ink/6 hover:text-ink", children: _jsx(X, { className: "size-(--size-icon-sm)", strokeWidth: 2 }) }))] }));
|
|
28
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Tone } from "../../foundations/types.js";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
export type FlagTone = Extract<Tone, "bad" | "warn" | "info" | "good">;
|
|
4
|
+
export type FlagProps = {
|
|
5
|
+
tone: FlagTone;
|
|
6
|
+
/** The sentence the mark stands for — «Sin existencias». It names the mark and heads
|
|
7
|
+
* the popover. */
|
|
8
|
+
label: string;
|
|
9
|
+
/** What explains it, inside the popover: the figures, what to do. */
|
|
10
|
+
children?: ReactNode;
|
|
11
|
+
};
|
|
12
|
+
/** A toned mark that opens its sentence. See the file's header. */
|
|
13
|
+
export declare function Flag({ tone, label, children }: FlagProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { tv } from "../../foundations/variants.js";
|
|
4
|
+
import { Popover } from "../overlay/Popover.js";
|
|
5
|
+
import { CircleAlert, CircleCheck, Info, TriangleAlert } from "lucide-react";
|
|
6
|
+
import { Button, Dialog, DialogTrigger, Heading } from "react-aria-components";
|
|
7
|
+
const ICON = {
|
|
8
|
+
bad: CircleAlert,
|
|
9
|
+
warn: TriangleAlert,
|
|
10
|
+
info: Info,
|
|
11
|
+
good: CircleCheck,
|
|
12
|
+
};
|
|
13
|
+
const mark = tv({
|
|
14
|
+
base: "inline-flex size-5 shrink-0 cursor-default items-center justify-center rounded-pill outline-0 transition-colors duration-(--duration-instant) forced-colors:text-[ButtonText]",
|
|
15
|
+
variants: {
|
|
16
|
+
tone: {
|
|
17
|
+
bad: "bg-bad/12 text-bad hover:bg-bad/20 aria-expanded:bg-bad/20",
|
|
18
|
+
warn: "bg-warn/15 text-warn hover:bg-warn/25 aria-expanded:bg-warn/25",
|
|
19
|
+
info: "bg-info/12 text-info hover:bg-info/20 aria-expanded:bg-info/20",
|
|
20
|
+
good: "bg-good/12 text-good hover:bg-good/20 aria-expanded:bg-good/20",
|
|
21
|
+
},
|
|
22
|
+
isFocusVisible: {
|
|
23
|
+
true: "outline-2 outline-offset-1 outline-primary forced-colors:outline-[Highlight]",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
/** A toned mark that opens its sentence. See the file's header. */
|
|
28
|
+
export function Flag({ tone, label, children }) {
|
|
29
|
+
const Icon = ICON[tone];
|
|
30
|
+
return (_jsxs(DialogTrigger, { children: [_jsx("span", { className: "inline-flex shrink-0", onClick: (e) => e.stopPropagation(), children: _jsx(Button, { "aria-label": label, className: (state) => mark({ ...state, tone }), children: _jsx(Icon, { className: "size-(--size-icon-sm)", strokeWidth: 2.25, "aria-hidden": true }) }) }), _jsx(Popover, { placement: "bottom start", className: "max-w-72 rounded-box", children: _jsxs(Dialog, { className: "flex flex-col gap-1 px-3 py-2.5 outline-0", children: [_jsx(Heading, { slot: "title", className: "text-meta font-medium text-ink", children: label }), children === undefined ? null : (_jsx("div", { className: "text-micro leading-relaxed text-muted", children: children }))] }) })] }));
|
|
31
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type LucideIcon } from "lucide-react";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
export declare function Skeleton({ rows, label, }: {
|
|
4
|
+
/** How many row-silhouettes. Six is a screen of a list, not a guess at
|
|
5
|
+
* the real count. */
|
|
6
|
+
rows?: number;
|
|
7
|
+
/** What a screen reader hears. The bars stay silent. */
|
|
8
|
+
label: string;
|
|
9
|
+
}): import("react").JSX.Element;
|
|
10
|
+
/** What is shown when there is nothing to show: a sentence, and the gesture
|
|
11
|
+
* that resolves it if there is one. Never a drawing. */
|
|
12
|
+
export declare function Empty({ sentence, action,
|
|
13
|
+
/** Centered for an empty list; left-aligned inside a block, where centering it
|
|
14
|
+
* detaches it from everything else. */
|
|
15
|
+
align, }: {
|
|
16
|
+
sentence: string;
|
|
17
|
+
action?: ReactNode;
|
|
18
|
+
align?: "center" | "start";
|
|
19
|
+
}): import("react").JSX.Element;
|
|
20
|
+
/**
|
|
21
|
+
* The sentence the backend wrote, whole and verbatim.
|
|
22
|
+
*
|
|
23
|
+
* Context: a hairline around a quiet red wash — it is not an alarm, it is an
|
|
24
|
+
* explanation. A bar on the left alone reads as a cut. Trimming the message or
|
|
25
|
+
* replacing it with one of our own is what forces you to go to the console to
|
|
26
|
+
* find out what happened.
|
|
27
|
+
*/
|
|
28
|
+
export declare function Failed({ heading, sentence, action, }: {
|
|
29
|
+
heading: string;
|
|
30
|
+
sentence: string;
|
|
31
|
+
action?: ReactNode;
|
|
32
|
+
}): import("react").JSX.Element;
|
|
33
|
+
/**
|
|
34
|
+
* Nobody knows how to draw this. It carries the full identifier because that is
|
|
35
|
+
* the only thing that makes it fixable.
|
|
36
|
+
*/
|
|
37
|
+
export declare function Missing({ heading, detail, icon: Icon, tone, }: {
|
|
38
|
+
heading: string;
|
|
39
|
+
detail?: string;
|
|
40
|
+
icon?: LucideIcon;
|
|
41
|
+
tone?: "warn" | "bad";
|
|
42
|
+
}): import("react").JSX.Element;
|
|
43
|
+
/** The icon `Missing` uses for what broke while being drawn, so that whoever
|
|
44
|
+
* composes does not have to choose it. */
|
|
45
|
+
export declare const ICONO_ROTO: LucideIcon;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { byDensity, useDensity } from "../../foundations/density.js";
|
|
4
|
+
import { AlertTriangle, PackageX } from "lucide-react";
|
|
5
|
+
/**
|
|
6
|
+
* Context: the ways a view says there is no content yet, and they are
|
|
7
|
+
* different because each one is fixed by a different person.
|
|
8
|
+
*
|
|
9
|
+
* Skeleton the content is coming. The silhouette stays so the eye
|
|
10
|
+
* knows where the rows will land.
|
|
11
|
+
* Empty there is nothing yet, or the filter left nothing. It is NORMAL.
|
|
12
|
+
* Failed something broke. It carries the backend's sentence whole and
|
|
13
|
+
* verbatim.
|
|
14
|
+
* Missing nobody knows how to resolve this. It is a wiring error.
|
|
15
|
+
*
|
|
16
|
+
* A generic «something went wrong» for all four forces you to open the console.
|
|
17
|
+
* A loading sentence drawn with `Empty` is the same mistake: it answers
|
|
18
|
+
* «there is nothing» while the data is still on its way.
|
|
19
|
+
*/
|
|
20
|
+
/** Quiet stand-in for a list that has not arrived.
|
|
21
|
+
*
|
|
22
|
+
* The bars are the row: a name on the left, a figure on the right. That is
|
|
23
|
+
* the scan of a list, held in place so the page does not jump when the rows
|
|
24
|
+
* land. There is no shimmer. A pulse already means «this state is alive»
|
|
25
|
+
* (`StatusDot`); a second one would spend motion on a wait. The sentence is
|
|
26
|
+
* for the screen reader — the bars are not the message. */
|
|
27
|
+
const SKELETON_NAME = ["w-48", "w-36", "w-56", "w-40", "w-44", "w-32"];
|
|
28
|
+
export function Skeleton({ rows = 6, label, }) {
|
|
29
|
+
const d = useDensity();
|
|
30
|
+
return (_jsxs("div", { "aria-busy": "true", className: "flex flex-col", children: [_jsx("span", { className: "sr-only", children: label }), Array.from({ length: rows }, (_, i) => (_jsxs("div", { "aria-hidden": true, className: `flex items-center ${byDensity(d, "gap-3 px-2 py-2", "gap-2 px-1.5 py-1")}`, children: [_jsx("span", { className: `h-3.5 rounded-control bg-ink/8 ${SKELETON_NAME[i % SKELETON_NAME.length]}` }), _jsx("span", { className: "ml-auto h-3 w-14 rounded-control bg-ink/5" })] }, i)))] }));
|
|
31
|
+
}
|
|
32
|
+
/** What is shown when there is nothing to show: a sentence, and the gesture
|
|
33
|
+
* that resolves it if there is one. Never a drawing. */
|
|
34
|
+
export function Empty({ sentence, action,
|
|
35
|
+
/** Centered for an empty list; left-aligned inside a block, where centering it
|
|
36
|
+
* detaches it from everything else. */
|
|
37
|
+
align = "center", }) {
|
|
38
|
+
const centered = align === "center";
|
|
39
|
+
return (_jsxs("div", { className: `flex flex-wrap gap-3 px-4 py-10 ${centered ? "flex-col items-center text-center" : "items-center"}`, children: [_jsx("p", { className: "max-w-short text-body text-muted", children: sentence }), action] }));
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The sentence the backend wrote, whole and verbatim.
|
|
43
|
+
*
|
|
44
|
+
* Context: a hairline around a quiet red wash — it is not an alarm, it is an
|
|
45
|
+
* explanation. A bar on the left alone reads as a cut. Trimming the message or
|
|
46
|
+
* replacing it with one of our own is what forces you to go to the console to
|
|
47
|
+
* find out what happened.
|
|
48
|
+
*/
|
|
49
|
+
export function Failed({ heading, sentence, action, }) {
|
|
50
|
+
return (_jsxs("div", { className: "rounded-control border border-bad/40 bg-bad/5 px-3 py-2.5", children: [_jsx("p", { className: "text-body font-medium text-bad", children: heading }), _jsx("p", { className: "mt-1.5 max-w-prose text-lead leading-relaxed text-ink", children: sentence }), action === undefined ? null : _jsx("div", { className: "mt-2.5", children: action })] }));
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Nobody knows how to draw this. It carries the full identifier because that is
|
|
54
|
+
* the only thing that makes it fixable.
|
|
55
|
+
*/
|
|
56
|
+
export function Missing({ heading, detail, icon: Icon = PackageX, tone = "warn", }) {
|
|
57
|
+
return (_jsxs("div", { className: "flex h-full min-h-24 flex-col items-center justify-center gap-2 p-6 text-center", children: [_jsx(Icon, { className: `size-(--size-icon-lg) ${tone === "bad" ? "text-bad" : "text-warn"}`, strokeWidth: 1.75 }), _jsx("p", { className: "text-meta font-medium text-ink", children: heading }), detail === undefined ? null : (_jsx("p", { className: "max-w-short font-mono text-micro break-all text-muted", children: detail }))] }));
|
|
58
|
+
}
|
|
59
|
+
/** The icon `Missing` uses for what broke while being drawn, so that whoever
|
|
60
|
+
* composes does not have to choose it. */
|
|
61
|
+
export const ICONO_ROTO = AlertTriangle;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Tone } from "../../foundations/types.js";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
export interface ToastSpec {
|
|
4
|
+
/** What happened, in a sentence that stands on its own. */
|
|
5
|
+
message: string;
|
|
6
|
+
tone?: Tone;
|
|
7
|
+
/** The long detail: the server's reason. It goes underneath and small. */
|
|
8
|
+
detail?: string;
|
|
9
|
+
/** Where to go, or how to undo. «Deshacer», «Ver el pago». With `href` a real
|
|
10
|
+
* link is drawn —it can be opened in another tab, copied, shared—; with
|
|
11
|
+
* `onSelect`, a button. Either closes the notice. */
|
|
12
|
+
action?: {
|
|
13
|
+
label: string;
|
|
14
|
+
onSelect?: () => void;
|
|
15
|
+
href?: string;
|
|
16
|
+
};
|
|
17
|
+
/** How long it lives, in ms. `0` = until it is closed. By default: the bad
|
|
18
|
+
* ones stay, the rest leave after 8 s. */
|
|
19
|
+
duration?: number;
|
|
20
|
+
/** The notice's identity. Two notices with the same key are the SAME one: the
|
|
21
|
+
* new one replaces the old one in its place, without stacking.
|
|
22
|
+
*
|
|
23
|
+
* Context: the server sends an import's progress over a channel that can
|
|
24
|
+
* repeat, and three identical stacked notices are not more information —
|
|
25
|
+
* they are the same information, three times, taking up the screen. */
|
|
26
|
+
key?: string;
|
|
27
|
+
}
|
|
28
|
+
interface Toast extends ToastSpec {
|
|
29
|
+
id: number;
|
|
30
|
+
}
|
|
31
|
+
interface ToastApi {
|
|
32
|
+
show: (spec: ToastSpec) => number;
|
|
33
|
+
dismiss: (id: number) => void;
|
|
34
|
+
}
|
|
35
|
+
export declare function useToast(): ToastApi;
|
|
36
|
+
/** The same API, or `null` when no `ToastProvider` is above. For a component that OFFERS
|
|
37
|
+
* undo when the screen has a notice stack and still renders alone — in a story, in a test,
|
|
38
|
+
* in an app that has not mounted the provider yet. */
|
|
39
|
+
export declare function useToastIfAny(): ToastApi | null;
|
|
40
|
+
export declare function ToastProvider({ children }: {
|
|
41
|
+
children: ReactNode;
|
|
42
|
+
}): import("react").JSX.Element;
|
|
43
|
+
/** The region of a given list of notices. `ToastProvider` draws it from its own state; it is
|
|
44
|
+
* exported so it can be drawn without the context — a story that wants to see the tones
|
|
45
|
+
* together and still.
|
|
46
|
+
*
|
|
47
|
+
* Context: React Aria's queue is the behaviour (timers, pause, landmark, focus) and this
|
|
48
|
+
* list is the content. Each notice in the list is mirrored into the queue once; when the
|
|
49
|
+
* queue closes one — its timer ran out, its × or its action was pressed — `onDismiss`
|
|
50
|
+
* hears it, and when the list drops one the queue closes it. */
|
|
51
|
+
export declare function ToastLayer({ toasts, onDismiss, }: {
|
|
52
|
+
toasts: readonly Toast[];
|
|
53
|
+
onDismiss: (id: number) => void;
|
|
54
|
+
}): import("react").JSX.Element | null;
|
|
55
|
+
export {};
|