@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,69 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { PAGE_SIZES } from "./pages.js";
|
|
4
|
+
import { fillCopy, useUiCopy } from "../../foundations/copy.js";
|
|
5
|
+
import { byDensity, useDensity } from "../../foundations/density.js";
|
|
6
|
+
import { bySurface, useSurface } from "../../foundations/surface.js";
|
|
7
|
+
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
8
|
+
import { useId, useState } from "react";
|
|
9
|
+
/* ---------------------------------------------------------------- *
|
|
10
|
+
* THE FOOTER AND ITS PAGINATION
|
|
11
|
+
* ---------------------------------------------------------------- */
|
|
12
|
+
/** How many of how many, and how I move to the next page.
|
|
13
|
+
*
|
|
14
|
+
* **It lives ONCE and it is mounted in two places.** The normal destination is
|
|
15
|
+
* the search bar: you search, you narrow down and you paginate on the same
|
|
16
|
+
* line, without looking down to the footer to discover there are another
|
|
17
|
+
* hundred and sixty. The footer (`ListingFooter`) existed only for the lists
|
|
18
|
+
* that do NOT have a search box — there is no narrowing line there to put it on.
|
|
19
|
+
*
|
|
20
|
+
* Two implementations of one pagination diverge at the first fix, so this is
|
|
21
|
+
* the only one and both places receive it.
|
|
22
|
+
*
|
|
23
|
+
* ── The range, not the page number ──────────────────────────────────
|
|
24
|
+
*
|
|
25
|
+
* «1–25 de 187» answers three questions —where I am, how much I see, how much
|
|
26
|
+
* there is— and «page 1 of 8» answers a single one, in units that depend on a
|
|
27
|
+
* page size the user did not choose.
|
|
28
|
+
*
|
|
29
|
+
* ── Why the page size is visible ────────────────────────────────────
|
|
30
|
+
*
|
|
31
|
+
* Because it is the difference between scrolling eight times and scrolling
|
|
32
|
+
* once. In a tool where somebody compares a hundred and eighty invoices,
|
|
33
|
+
* leaving it fixed at 25 forces paginating over something that is not a
|
|
34
|
+
* product decision. */
|
|
35
|
+
export function Pagination({ from, to, total, pageSize, pageSizes = PAGE_SIZES, onPageSizeChange, onPrev, onNext, surface, }) {
|
|
36
|
+
const d = useDensity();
|
|
37
|
+
const copy = useUiCopy();
|
|
38
|
+
const activeSurface = useSurface(surface);
|
|
39
|
+
const [editing, setEditing] = useState(false);
|
|
40
|
+
const [draft, setDraft] = useState("");
|
|
41
|
+
const listId = useId();
|
|
42
|
+
const faint = bySurface(activeSurface, "text-muted", "text-chrome-muted");
|
|
43
|
+
const strong = bySurface(activeSurface, "text-ink", "text-chrome-ink");
|
|
44
|
+
const arrow = `inline-flex size-(--size-control-sm) items-center justify-center rounded-pill transition-colors duration-(--duration-instant) disabled:opacity-30 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary ${bySurface(activeSurface, "text-muted not-disabled:hover:bg-ink/6 not-disabled:hover:text-ink", "text-chrome-muted not-disabled:hover:bg-chrome-ink/12 not-disabled:hover:text-chrome-ink")}`;
|
|
45
|
+
const canEdit = pageSize !== undefined && onPageSizeChange !== undefined && d === "comfortable";
|
|
46
|
+
const sizes = pageSize !== undefined && !pageSizes.includes(pageSize)
|
|
47
|
+
? [...pageSizes, pageSize]
|
|
48
|
+
: pageSizes;
|
|
49
|
+
function commitSize(raw) {
|
|
50
|
+
const next = Number(raw);
|
|
51
|
+
if (!Number.isFinite(next) || next < 1) {
|
|
52
|
+
setEditing(false);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
onPageSizeChange?.(Math.floor(next));
|
|
56
|
+
setEditing(false);
|
|
57
|
+
}
|
|
58
|
+
return (_jsxs("div", { className: `flex shrink-0 items-center ${byDensity(d, "gap-1", "gap-0.5")}`, children: [editing && canEdit ? (_jsxs(_Fragment, { children: [_jsx("input", { autoFocus: true, type: "number", min: 1, step: 1, list: listId, value: draft, onChange: (e) => setDraft(e.target.value), onBlur: () => commitSize(draft), onKeyDown: (e) => {
|
|
59
|
+
if (e.key === "Enter") {
|
|
60
|
+
e.preventDefault();
|
|
61
|
+
commitSize(draft);
|
|
62
|
+
}
|
|
63
|
+
if (e.key === "Escape")
|
|
64
|
+
setEditing(false);
|
|
65
|
+
}, "aria-label": copy.pager.size, className: `h-(--size-control-sm) w-24 rounded-control border px-1.5 text-micro tabular-nums focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-primary ${bySurface(activeSurface, "border-line-strong bg-surface text-ink", "border-chrome-ink/45 bg-chrome-ink/8 text-chrome-ink")}` }), _jsx("datalist", { id: listId, children: sizes.map((n) => (_jsx("option", { value: n }, n))) })] })) : (_jsxs("span", { className: `text-micro tabular-nums whitespace-nowrap ${faint}`, children: [canEdit ? (_jsxs("button", { type: "button", onClick: () => {
|
|
66
|
+
setDraft(String(pageSize));
|
|
67
|
+
setEditing(true);
|
|
68
|
+
}, title: fillCopy(copy.pager.sizeHint, { size: pageSize ?? 0 }), className: `rounded-control underline decoration-dotted underline-offset-4 transition-colors duration-(--duration-instant) ${strong} ${bySurface(activeSurface, "decoration-line-strong hover:decoration-primary", "decoration-chrome-ink/40 hover:decoration-chrome-link")} focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-primary`, children: [from.toLocaleString(copy.locale), "\u2013", to.toLocaleString(copy.locale)] })) : (_jsxs("span", { className: strong, children: [from.toLocaleString(copy.locale), "\u2013", to.toLocaleString(copy.locale)] })), _jsx("span", { className: "mx-1", children: "/" }), total.toLocaleString(copy.locale)] })), _jsxs("span", { className: "flex items-center", children: [_jsx("button", { type: "button", onClick: onPrev, disabled: from <= 1, "aria-label": copy.pager.prev, title: copy.pager.prev, className: arrow, children: _jsx(ChevronLeft, { className: "size-(--size-icon-md)", strokeWidth: 2 }) }), _jsx("button", { type: "button", onClick: onNext, disabled: to >= total, "aria-label": copy.pager.next, title: copy.pager.next, className: arrow, children: _jsx(ChevronRight, { className: "size-(--size-icon-md)", strokeWidth: 2 }) })] })] }));
|
|
69
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { LucideIcon } from "lucide-react";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
/**
|
|
4
|
+
* One record as a card — the board presentation of a list.
|
|
5
|
+
*
|
|
6
|
+
* Context: the board drew this card inline, and a list that wanted its own card wrote its
|
|
7
|
+
* own `div` — which looked the same and could not be opened: the default card was a button
|
|
8
|
+
* with `onOpen`, the custom one was not. A card is the record's door in the board the way a
|
|
9
|
+
* row is in the table, so it is a component and it is always pressable when there is
|
|
10
|
+
* somewhere to go.
|
|
11
|
+
*
|
|
12
|
+
* Its hierarchy is the row's: the state first (what it is), the name as the entry point,
|
|
13
|
+
* the facts below in the quiet size.
|
|
14
|
+
*/
|
|
15
|
+
export declare function RecordCard({ title, tags, meta, figure, media, onOpen, }: {
|
|
16
|
+
/** The record's name — the entry point of the card. */
|
|
17
|
+
title: ReactNode;
|
|
18
|
+
/** The state, as a `Tag`: read before the name, the way a board is scanned. */
|
|
19
|
+
tags?: ReactNode;
|
|
20
|
+
/** The facts that tell two cards apart — a number, a date. */
|
|
21
|
+
meta?: ReactNode;
|
|
22
|
+
/** The record's key figure, on the right of the name — an amount. */
|
|
23
|
+
figure?: ReactNode;
|
|
24
|
+
/** The record's picture — a product photo, a customer's logo — or its icon. A board of
|
|
25
|
+
* products is recognised by the pictures before a single name is read. */
|
|
26
|
+
media?: {
|
|
27
|
+
src?: string;
|
|
28
|
+
alt?: string;
|
|
29
|
+
icon?: LucideIcon;
|
|
30
|
+
};
|
|
31
|
+
/** Opens the record. Without it the card is not pressable. */
|
|
32
|
+
onOpen?: () => void;
|
|
33
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { Thumb } from "../../primitives/cell/CellStack.js";
|
|
4
|
+
/**
|
|
5
|
+
* One record as a card — the board presentation of a list.
|
|
6
|
+
*
|
|
7
|
+
* Context: the board drew this card inline, and a list that wanted its own card wrote its
|
|
8
|
+
* own `div` — which looked the same and could not be opened: the default card was a button
|
|
9
|
+
* with `onOpen`, the custom one was not. A card is the record's door in the board the way a
|
|
10
|
+
* row is in the table, so it is a component and it is always pressable when there is
|
|
11
|
+
* somewhere to go.
|
|
12
|
+
*
|
|
13
|
+
* Its hierarchy is the row's: the state first (what it is), the name as the entry point,
|
|
14
|
+
* the facts below in the quiet size.
|
|
15
|
+
*/
|
|
16
|
+
export function RecordCard({ title, tags, meta, figure, media, onOpen, }) {
|
|
17
|
+
const body = (_jsxs(_Fragment, { children: [media === undefined ? null : (_jsx(Thumb, { size: "lg", ...(media.src === undefined ? {} : { src: media.src }), ...(media.alt === undefined ? {} : { alt: media.alt }), ...(media.icon === undefined ? {} : { icon: media.icon }) })), tags, _jsxs("div", { className: "flex min-w-0 items-baseline justify-between gap-3", children: [_jsx("p", { className: "min-w-0 truncate text-lead font-medium text-ink", children: title }), figure === undefined ? null : (_jsx("p", { className: "shrink-0 text-lead font-semibold tabular-nums text-ink", children: figure }))] }), meta === undefined ? null : (_jsx("p", { className: "text-micro tabular-nums text-muted", children: meta }))] }));
|
|
18
|
+
const box = "flex min-w-0 flex-col gap-2 rounded-box border border-line bg-surface p-4 text-left";
|
|
19
|
+
if (onOpen === undefined)
|
|
20
|
+
return _jsx("div", { className: box, children: body });
|
|
21
|
+
return (_jsx("button", { type: "button", onClick: onOpen, className: `${box} transition-colors duration-(--duration-instant) hover:border-line-strong focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-primary`, children: body }));
|
|
22
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
export declare function RecordLink({ children, onClick, title, }: {
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
onClick?: () => void;
|
|
5
|
+
/** The whole name, when the cell shows only what fits. */
|
|
6
|
+
title?: string;
|
|
7
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
/* ==================================================================== *
|
|
4
|
+
* THE NAME THAT OPENS THE RECORD.
|
|
5
|
+
*
|
|
6
|
+
* Every list was drawing this button by hand, and each one picked a
|
|
7
|
+
* different cutoff: 28ch, 32ch, 42ch. The name is a link — blue, because
|
|
8
|
+
* it goes somewhere — and it truncates at the cell, not at a number
|
|
9
|
+
* somebody guessed. The click does not also open the row: the name and
|
|
10
|
+
* the row are two gestures, and both have to arrive.
|
|
11
|
+
* ==================================================================== */
|
|
12
|
+
export function RecordLink({ children, onClick, title, }) {
|
|
13
|
+
/* With nowhere to go it is the record's name, not a button: a control that looks pressable
|
|
14
|
+
and does nothing is a dead end. */
|
|
15
|
+
if (onClick === undefined) {
|
|
16
|
+
return (_jsx("span", { ...(title === undefined ? {} : { title }), className: "block min-w-0 max-w-full truncate text-meta font-medium text-ink", children: children }));
|
|
17
|
+
}
|
|
18
|
+
return (_jsx("button", { type: "button", ...(title === undefined ? {} : { title }), onClick: (event) => {
|
|
19
|
+
event.stopPropagation();
|
|
20
|
+
onClick?.();
|
|
21
|
+
}, className: "block min-w-0 max-w-full truncate rounded-control text-left text-meta font-medium text-primary transition-colors duration-(--duration-instant) hover:underline hover:underline-offset-4 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-primary", children: children }));
|
|
22
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { type MenuGroup } from "../../primitives/menu/Menu.js";
|
|
2
|
+
import type { LucideIcon } from "lucide-react";
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
/** A record in two lines: big name on top, metadata below.
|
|
5
|
+
*
|
|
6
|
+
* ── The name opens, the body peeks ──────────────────────────────────
|
|
7
|
+
*
|
|
8
|
+
* Two different targets and zero waiting. The name is a LINK and opens with
|
|
9
|
+
* one click; the rest of the row does a preview. It used to open with a double
|
|
10
|
+
* click, and to be able to tell it apart from the single one ALL clicks had to
|
|
11
|
+
* be delayed by 220 ms — a name painted as a link discovers itself, «try
|
|
12
|
+
* double clicking» is written nowhere.
|
|
13
|
+
*
|
|
14
|
+
* The gesture is invisible if nothing says so, so on hover `⇱ abrir` appears:
|
|
15
|
+
* it is learned in one hover and never has to be explained.
|
|
16
|
+
*
|
|
17
|
+
* ── The metadata is in plain sight ──────────────────────────────────
|
|
18
|
+
*
|
|
19
|
+
* «some metadata that might help is not visible» — and it was literal: to know
|
|
20
|
+
* whether `FAC/2026/0118` was paid or overdue you had to open it. */
|
|
21
|
+
export declare function Row({ icon: Icon, name, onOpen, onPeek, tags, meta, selected, onSelectedChange, actions, more, onMoreSelect, openHint, children, copy: copyAs, }: {
|
|
22
|
+
icon?: LucideIcon;
|
|
23
|
+
name: string;
|
|
24
|
+
/** The copy button among the row's actions: on by default, copying the row's name — the
|
|
25
|
+
* number of an invoice, a customer — without opening it. A string copies that instead;
|
|
26
|
+
* `false` removes it. */
|
|
27
|
+
copy?: string | false;
|
|
28
|
+
onOpen?: () => void;
|
|
29
|
+
onPeek?: () => void;
|
|
30
|
+
tags?: ReactNode;
|
|
31
|
+
/** The secondary data, already formatted. In mono: it aligns across rows. */
|
|
32
|
+
meta?: string;
|
|
33
|
+
selected?: boolean;
|
|
34
|
+
onSelectedChange?: (next: boolean) => void;
|
|
35
|
+
actions?: ReactNode;
|
|
36
|
+
/** The `⋯` menu. **Without this the button is not drawn**: there used to be a
|
|
37
|
+
* fixed one that opened nothing — a control that exists and does nothing
|
|
38
|
+
* teaches people not to press it, and after that nobody presses the one that
|
|
39
|
+
* does work. */
|
|
40
|
+
more?: readonly MenuGroup[];
|
|
41
|
+
onMoreSelect?: (id: string) => void;
|
|
42
|
+
/** What happens on open, when «as a panel» is not true: «open the order that
|
|
43
|
+
* produced it». It is the `title` and the accessible name. */
|
|
44
|
+
openHint?: string;
|
|
45
|
+
/** What the resource PRODUCED, hanging below. Context: the indent and the
|
|
46
|
+
* rule are put there by the system and not by the view, because the
|
|
47
|
+
* relationship «this came out of that» has to look the same in the four
|
|
48
|
+
* lists that need it. */
|
|
49
|
+
children?: ReactNode;
|
|
50
|
+
}): import("react").JSX.Element;
|
|
51
|
+
/**
|
|
52
|
+
* EL ENCABEZADO DE LA PRESENTACIÓN DE FILAS.
|
|
53
|
+
*
|
|
54
|
+
* Context: la tabla y la planilla dicen qué es cada columna; la lista de filas
|
|
55
|
+
* no decía nada. Ochenta renglones con «Ferretería Sur · 13 sept · Bs 12.480» a la
|
|
56
|
+
* derecha y una ficha al final, y había que deducir qué era cada número — o
|
|
57
|
+
* cambiar a la tabla para averiguarlo, que es cambiar de presentación para leer
|
|
58
|
+
* la misma pantalla.
|
|
59
|
+
*
|
|
60
|
+
* **No son columnas: son las ZONAS de la fila, rotuladas.** Una fila tiene un
|
|
61
|
+
* nombre que se estira, un `meta` que es UNA cadena ya formateada, y una
|
|
62
|
+
* columna de fichas de ancho fijo. Rotularlas como si fueran tres columnas
|
|
63
|
+
* alineadas sería mentir sobre lo que hay debajo: el `meta` de una fila puede
|
|
64
|
+
* decir tres cosas y el de otra dos.
|
|
65
|
+
*
|
|
66
|
+
* Por eso calca la caja de `Row` —mismo padding, mismos huecos, mismos
|
|
67
|
+
* espaciadores de casilla, ícono y acciones— en vez de declarar un ancho
|
|
68
|
+
* propio. Si `Row` cambia su geometría, esto la sigue.
|
|
69
|
+
*/
|
|
70
|
+
export declare function RowHeader({ name, meta, tags, withSelection, withIcon, withActions, }: {
|
|
71
|
+
name: string;
|
|
72
|
+
meta?: string;
|
|
73
|
+
tags?: string;
|
|
74
|
+
/** Los espaciadores de la izquierda. Los pone quien dibuja las filas, porque
|
|
75
|
+
* es el único que sabe si esta lista elige y si sus filas tienen ícono. */
|
|
76
|
+
withSelection?: boolean;
|
|
77
|
+
withIcon?: boolean;
|
|
78
|
+
/** La zona de acciones de la derecha ocupa su lugar aunque esté invisible
|
|
79
|
+
* —así nada se corre al pasar el puntero—, así que el rótulo de las fichas
|
|
80
|
+
* queda desalineado si acá no se reserva lo mismo. */
|
|
81
|
+
withActions?: boolean;
|
|
82
|
+
}): import("react").JSX.Element;
|
|
83
|
+
/**
|
|
84
|
+
* The header of a group inside a list.
|
|
85
|
+
*
|
|
86
|
+
* Context: grouping existed ONLY inside `DataTable`, so changing presentation
|
|
87
|
+
* —from table to rows— lost the grouping. Grouping belongs to the SET and not
|
|
88
|
+
* to the presentation; that is why it lives here.
|
|
89
|
+
*
|
|
90
|
+
* **`field` is drawn, and it is not decoration.** With one level nobody needed
|
|
91
|
+
* it: there was a single axis and everyone knew what it was. With several,
|
|
92
|
+
* «2026-09» does not say whether it is when the thing was registered or when it
|
|
93
|
+
* ran, and two consecutive headers can read as siblings when one is inside the
|
|
94
|
+
* other. The field names the cut; the indent says how deep it is.
|
|
95
|
+
*/
|
|
96
|
+
export declare function GroupHeader({ field, label, count, detail, depth, open, onToggle, }: {
|
|
97
|
+
/** By which field this pile was cut. Without it a value is ambiguous as soon
|
|
98
|
+
* as there is more than one level. */
|
|
99
|
+
field?: string;
|
|
100
|
+
label: string;
|
|
101
|
+
count?: number;
|
|
102
|
+
/** What the pile ADDS UP TO, already written: «412 facturas · Bs 1.204.933». A
|
|
103
|
+
* folded pile that only says how many there are hides the answer instead of
|
|
104
|
+
* summarising it. */
|
|
105
|
+
detail?: string;
|
|
106
|
+
/** How deep the pile is. `0` is the first level. */
|
|
107
|
+
depth?: number;
|
|
108
|
+
open?: boolean;
|
|
109
|
+
onToggle?: () => void;
|
|
110
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { RecordLink } from "./RecordLink.js";
|
|
4
|
+
import { fillCopy, useUiCopy } from "../../foundations/copy.js";
|
|
5
|
+
import { byDensity, useDensity } from "../../foundations/density.js";
|
|
6
|
+
import { stickyTop } from "../../foundations/sticky.js";
|
|
7
|
+
import { Menu } from "../../primitives/menu/Menu.js";
|
|
8
|
+
import { CopyButton } from "../../primitives/value/Copyable.js";
|
|
9
|
+
import { ChevronRight, MoreHorizontal, SquareArrowOutUpRight } from "lucide-react";
|
|
10
|
+
/* ---------------------------------------------------------------- *
|
|
11
|
+
* THE RICH ROW — the other density
|
|
12
|
+
* ---------------------------------------------------------------- */
|
|
13
|
+
/** A record in two lines: big name on top, metadata below.
|
|
14
|
+
*
|
|
15
|
+
* ── The name opens, the body peeks ──────────────────────────────────
|
|
16
|
+
*
|
|
17
|
+
* Two different targets and zero waiting. The name is a LINK and opens with
|
|
18
|
+
* one click; the rest of the row does a preview. It used to open with a double
|
|
19
|
+
* click, and to be able to tell it apart from the single one ALL clicks had to
|
|
20
|
+
* be delayed by 220 ms — a name painted as a link discovers itself, «try
|
|
21
|
+
* double clicking» is written nowhere.
|
|
22
|
+
*
|
|
23
|
+
* The gesture is invisible if nothing says so, so on hover `⇱ abrir` appears:
|
|
24
|
+
* it is learned in one hover and never has to be explained.
|
|
25
|
+
*
|
|
26
|
+
* ── The metadata is in plain sight ──────────────────────────────────
|
|
27
|
+
*
|
|
28
|
+
* «some metadata that might help is not visible» — and it was literal: to know
|
|
29
|
+
* whether `FAC/2026/0118` was paid or overdue you had to open it. */
|
|
30
|
+
export function Row({ icon: Icon, name, onOpen, onPeek, tags, meta, selected, onSelectedChange, actions, more, onMoreSelect, openHint, children, copy: copyAs, }) {
|
|
31
|
+
const d = useDensity();
|
|
32
|
+
const copy = useUiCopy();
|
|
33
|
+
const openAs = openHint ?? copy.shell.openAsPanel;
|
|
34
|
+
const row = (
|
|
35
|
+
/* A `div` and not an `li`: whoever composes decides whether this is a list,
|
|
36
|
+
a table or a branch of a tree. And the zone that peeks is a `div` with
|
|
37
|
+
`onClick` and not a `button` — the actions are buttons too, and a button
|
|
38
|
+
inside another one is invalid HTML: the browser takes it apart and the
|
|
39
|
+
clicks get lost. */
|
|
40
|
+
_jsx("div", { className: "border-b border-line px-1 py-0.5 last:border-b-0", children: _jsxs("div", { "data-slot": "row",
|
|
41
|
+
/* The hairline stays on the shell, full width: eighty similar names with
|
|
42
|
+
no rule between them read as a paragraph. The wash is the inner row,
|
|
43
|
+
inset and rounded, so the hover is a mark and not a stripe. The inner
|
|
44
|
+
padding is shorter by exactly that inset, so the text does not move. */
|
|
45
|
+
className: `group/row flex min-w-0 items-center rounded-control transition-colors duration-(--duration-instant) hover:bg-ink/4 ${byDensity(d, "gap-3 px-2 py-2", "gap-2 px-1.5 py-1")}`,
|
|
46
|
+
/* `onPeek` con `onOpen` de reserva: quien sólo declara `onOpen` —lo
|
|
47
|
+
natural si nunca oyó hablar de la vista previa— tiene que poder abrir
|
|
48
|
+
igual haciendo click en cualquier parte de la fila, no sólo en el
|
|
49
|
+
nombre. Con la vista previa apagada (`PeekLayer.tsx`) esto es hoy el
|
|
50
|
+
caso de todos: un click en la fila abre, sin más. */
|
|
51
|
+
onClick: onPeek ?? onOpen, role: "presentation", children: [onSelectedChange === undefined ? null : (_jsx("input", { type: "checkbox", checked: selected ?? false, "aria-label": fillCopy(copy.selection.named, { name }), onChange: (e) => onSelectedChange(e.target.checked), onClick: (e) => e.stopPropagation(), className: "size-3.5 shrink-0 cursor-pointer accent-primary" })), Icon === undefined ? null : (_jsx(Icon, { className: "size-(--size-icon-md) shrink-0 text-muted", strokeWidth: 1.75, "aria-hidden": true })), _jsxs("div", { className: `flex min-w-0 shrink grow basis-(--container-name) flex-wrap items-center gap-y-0 ${byDensity(d, "gap-x-3", "gap-x-2")}`, children: [_jsx("span", { className: "min-w-0 grow basis-(--container-name)", children: _jsx(RecordLink, { ...((onOpen ?? onPeek) === undefined ? {} : { onClick: (onOpen ?? onPeek) }), title: name, children: name }) }), meta === undefined ? null : (
|
|
52
|
+
/* Wrapped or not, the metadata is still there —it is what saves
|
|
53
|
+
having to open a set to know whether it is a thousand rows or a
|
|
54
|
+
million— and it truncates instead of wrapping into itself: three
|
|
55
|
+
lines per row in a 480 px drawer means three rows fit on screen. */
|
|
56
|
+
_jsx("span", { className: "min-w-0 truncate text-micro tabular-nums text-muted", children: meta }))] }), tags === undefined ? null : (_jsx("span", { className: "flex min-w-fit shrink basis-(--container-tags) items-center justify-end gap-1.5", children: tags })), _jsxs("span", { className: "flex shrink-0 items-center gap-1 opacity-0 transition-opacity duration-(--duration-instant) group-hover/row:opacity-100 focus-within:opacity-100 pointer-coarse:opacity-100", children: [actions, copyAs === false ? null : (_jsx(CopyButton, { value: copyAs ?? name, label: fillCopy(copy.copyable.field, { label: name }) })), onOpen === undefined ? null : (_jsx("button", { type: "button", onClick: (e) => {
|
|
57
|
+
e.stopPropagation();
|
|
58
|
+
onOpen();
|
|
59
|
+
}, title: openAs, "aria-label": `${openAs}: ${name}`, className: "inline-flex size-(--size-control-sm) items-center justify-center rounded-pill text-muted transition-colors duration-(--duration-instant) hover:bg-ink/6 hover:text-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary", children: _jsx(SquareArrowOutUpRight, { className: "size-(--size-icon-sm)", strokeWidth: 2 }) })), more === undefined || more.length === 0 ? null : (_jsx("span", { onClick: (e) => e.stopPropagation(), role: "presentation", children: _jsx(Menu, { label: fillCopy(copy.selection.moreOf, { name }), groups: more, icon: MoreHorizontal, iconOnly: true, chevron: false, align: "end", width: "sm", variant: "text", ...(onMoreSelect === undefined ? {} : { onSelect: onMoreSelect }) }) }))] })] }) }));
|
|
60
|
+
if (children === undefined)
|
|
61
|
+
return row;
|
|
62
|
+
/* What the resource produced, hanging below. The indent reaches up to where
|
|
63
|
+
the name starts —not up to the edge— so the rule falls exactly below the
|
|
64
|
+
icon and the relationship reads without reading: this came out of that.
|
|
65
|
+
|
|
66
|
+
The child is NOT another `Row`: an invoice line does not open, does not get
|
|
67
|
+
selected and has no actions of its own. Treating it as a row would make it promise
|
|
68
|
+
all of that. */
|
|
69
|
+
return (_jsxs("div", { "data-slot": "branch", className: "flex min-w-0 flex-col", children: [row, _jsx("div", { className: `flex min-w-0 flex-col gap-1 border-l border-line-strong ${byDensity(d, "mb-2 ml-8 pb-0.5 pl-3 pr-3", "mb-1.5 ml-6 pl-2.5 pr-2.5")}`, children: children })] }));
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* EL ENCABEZADO DE LA PRESENTACIÓN DE FILAS.
|
|
73
|
+
*
|
|
74
|
+
* Context: la tabla y la planilla dicen qué es cada columna; la lista de filas
|
|
75
|
+
* no decía nada. Ochenta renglones con «Ferretería Sur · 13 sept · Bs 12.480» a la
|
|
76
|
+
* derecha y una ficha al final, y había que deducir qué era cada número — o
|
|
77
|
+
* cambiar a la tabla para averiguarlo, que es cambiar de presentación para leer
|
|
78
|
+
* la misma pantalla.
|
|
79
|
+
*
|
|
80
|
+
* **No son columnas: son las ZONAS de la fila, rotuladas.** Una fila tiene un
|
|
81
|
+
* nombre que se estira, un `meta` que es UNA cadena ya formateada, y una
|
|
82
|
+
* columna de fichas de ancho fijo. Rotularlas como si fueran tres columnas
|
|
83
|
+
* alineadas sería mentir sobre lo que hay debajo: el `meta` de una fila puede
|
|
84
|
+
* decir tres cosas y el de otra dos.
|
|
85
|
+
*
|
|
86
|
+
* Por eso calca la caja de `Row` —mismo padding, mismos huecos, mismos
|
|
87
|
+
* espaciadores de casilla, ícono y acciones— en vez de declarar un ancho
|
|
88
|
+
* propio. Si `Row` cambia su geometría, esto la sigue.
|
|
89
|
+
*/
|
|
90
|
+
export function RowHeader({ name, meta, tags, withSelection = false, withIcon = false, withActions = false, }) {
|
|
91
|
+
const d = useDensity();
|
|
92
|
+
const rotulo = "truncate text-micro font-medium text-muted";
|
|
93
|
+
return (_jsxs("div", { "data-slot": "row-header",
|
|
94
|
+
/* Se pega debajo de la barra de la lista, como el encabezado de la tabla
|
|
95
|
+
y el de la planilla. Ver `system/sticky.tsx`. */
|
|
96
|
+
className: `sticky z-10 flex min-w-0 items-center border-b border-line-strong bg-ground ${byDensity(d, "gap-3 px-3 py-2", "gap-2 px-2.5 py-1.5")}`, style: stickyTop, role: "presentation", children: [!withSelection ? null : _jsx("span", { className: "size-3.5 shrink-0", "aria-hidden": true }), !withIcon ? null : _jsx("span", { className: "size-(--size-icon-md) shrink-0", "aria-hidden": true }), _jsxs("div", { className: `flex min-w-0 shrink grow basis-(--container-name) flex-wrap items-center gap-y-0 ${byDensity(d, "gap-x-3", "gap-x-2")}`, children: [_jsx("span", { className: `min-w-0 grow basis-(--container-name) ${rotulo}`, children: name }), meta === undefined ? null : _jsx("span", { className: `min-w-0 ${rotulo}`, children: meta })] }), tags === undefined ? null : (_jsx("span", { className: "flex min-w-fit shrink basis-(--container-tags) items-center justify-end", children: _jsx("span", { className: rotulo, children: tags }) })), !withActions ? null : (_jsx("span", { className: "size-(--size-control-sm) shrink-0", "aria-hidden": true }))] }));
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The header of a group inside a list.
|
|
100
|
+
*
|
|
101
|
+
* Context: grouping existed ONLY inside `DataTable`, so changing presentation
|
|
102
|
+
* —from table to rows— lost the grouping. Grouping belongs to the SET and not
|
|
103
|
+
* to the presentation; that is why it lives here.
|
|
104
|
+
*
|
|
105
|
+
* **`field` is drawn, and it is not decoration.** With one level nobody needed
|
|
106
|
+
* it: there was a single axis and everyone knew what it was. With several,
|
|
107
|
+
* «2026-09» does not say whether it is when the thing was registered or when it
|
|
108
|
+
* ran, and two consecutive headers can read as siblings when one is inside the
|
|
109
|
+
* other. The field names the cut; the indent says how deep it is.
|
|
110
|
+
*/
|
|
111
|
+
export function GroupHeader({ field, label, count, detail, depth = 0, open = true, onToggle, }) {
|
|
112
|
+
const d = useDensity();
|
|
113
|
+
const copy = useUiCopy();
|
|
114
|
+
const content = (_jsxs(_Fragment, { children: [onToggle === undefined ? null : (_jsx(ChevronRight, { className: `size-(--size-icon-sm) shrink-0 text-muted transition-transform duration-(--duration-instant) ${open ? "rotate-90" : ""}`, strokeWidth: 2, "aria-hidden": true })), field === undefined ? null : (_jsx("span", { className: "shrink-0 text-micro text-muted/70", children: field })), _jsx("span", { className: "truncate text-micro font-medium text-ink", children: label }), count === undefined ? null : (_jsx("span", { className: "inline-flex shrink-0 items-center rounded-pill border border-line-strong bg-ink/5 px-2 py-0.5 text-micro tabular-nums text-muted", children: count.toLocaleString(copy.locale) })), detail === undefined ? null : (_jsx("span", { className: "shrink-0 text-micro tabular-nums text-muted", children: detail }))] }));
|
|
115
|
+
/* La sangría va en el relleno de la izquierda y no en un margen: el
|
|
116
|
+
encabezado tiene fondo y borde inferior, y un margen los cortaría antes del
|
|
117
|
+
borde de la lista — la banda dejaría de leerse como una franja. El hover
|
|
118
|
+
no reemplaza ese fondo: es una marca metida adentro, redondeada. */
|
|
119
|
+
const box = `flex min-w-0 items-center gap-2 border-b border-line bg-ground ${byDensity(d, "px-3 py-1.5", "px-2.5 py-1")}`;
|
|
120
|
+
const indent = depth === 0 ? undefined : { paddingLeft: `calc(var(--spacing-indent) * ${depth})` };
|
|
121
|
+
const mark = (_jsx("span", { "aria-hidden": true, className: "pointer-events-none absolute inset-x-1 inset-y-0.5 rounded-control group-hover/head:bg-ink/4" }));
|
|
122
|
+
return onToggle === undefined ? (_jsx("div", { className: box, style: indent, children: content })) : (_jsxs("button", { type: "button", onClick: onToggle, "aria-expanded": open, style: indent, className: `group/head relative ${box} w-full text-left focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-primary`, children: [mark, content] }));
|
|
123
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { Surface } from "../../foundations/types.js";
|
|
2
|
+
import type { SortState } from "../../primitives/table/Table.js";
|
|
3
|
+
/**
|
|
4
|
+
* ORDENAR, cuando no hay encabezados donde hacer clic.
|
|
5
|
+
*
|
|
6
|
+
* Context: ordenar era una capacidad de `DataTable` y de nadie más. Se hacía
|
|
7
|
+
* clic en el encabezado de la columna, que es la forma correcta **cuando hay
|
|
8
|
+
* encabezados**. En filas, en tablero y en la planilla no los hay, así que
|
|
9
|
+
* cambiar de presentación no perdía sólo el orden puesto: perdía la posibilidad
|
|
10
|
+
* de ordenar. Es exactamente el mismo drift que tenía la agrupación.
|
|
11
|
+
*
|
|
12
|
+
* El orden es del CONJUNTO y no de la presentación, igual que los niveles y que
|
|
13
|
+
* lo plegado. Por eso el control vive en la barra y no adentro de la tabla, y
|
|
14
|
+
* por eso la tabla sigue teniendo además sus encabezados: son dos puertas a lo
|
|
15
|
+
* mismo, y la de los encabezados es más rápida cuando existe.
|
|
16
|
+
*
|
|
17
|
+
* ── Por qué es un `Menu` y el agrupador no ──────────────────────────
|
|
18
|
+
*
|
|
19
|
+
* Porque ordenar SÍ es elegir una opción: un campo y una dirección. No hay orden
|
|
20
|
+
* ni jerarquía que expresar —ordenar por dos campos a la vez es un desempate,
|
|
21
|
+
* que es otra pregunta y la hace el motor— así que una lista de tildes alcanza y
|
|
22
|
+
* no hace falta un editor.
|
|
23
|
+
*/
|
|
24
|
+
export type SortField<T = unknown> = {
|
|
25
|
+
field: string;
|
|
26
|
+
label: string;
|
|
27
|
+
/**
|
|
28
|
+
* Por qué valor se ordena, cuando ordena el NAVEGADOR.
|
|
29
|
+
*
|
|
30
|
+
* Ausente = ya viene ordenado de donde salió. Es la misma división que tiene
|
|
31
|
+
* el agrupado con `piles`: un conjunto que el servidor pagina no se puede
|
|
32
|
+
* ordenar acá —ordenar doce filas de ciento ochenta y siete da un orden
|
|
33
|
+
* falso— así que ahí el orden viaja en la lectura y esto sólo lo refleja.
|
|
34
|
+
*/
|
|
35
|
+
value?: (item: T) => string | number | null | undefined;
|
|
36
|
+
/** Qué quiere decir «ascendente» para este campo. Sin esto, «A → Z».
|
|
37
|
+
* Una fecha asciende de vieja a nueva y un importe de chico a grande, y
|
|
38
|
+
* decirlo evita el clic de prueba. */
|
|
39
|
+
ascLabel?: string;
|
|
40
|
+
descLabel?: string;
|
|
41
|
+
};
|
|
42
|
+
export type SortMenuProps = {
|
|
43
|
+
/** Por qué campos se puede ordenar. Sin ninguno, el control no se dibuja. */
|
|
44
|
+
fields: readonly SortField<never>[];
|
|
45
|
+
sort: SortState | null;
|
|
46
|
+
onSortChange: (next: SortState | null) => void;
|
|
47
|
+
surface?: Surface;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Las filas, ordenadas acá.
|
|
51
|
+
*
|
|
52
|
+
* Sólo cuando el campo activo declara `value`; si no, se devuelven tal cual —
|
|
53
|
+
* ordenar una página de un conjunto que se pagina afuera produce un orden que es
|
|
54
|
+
* mentira, porque ordena las doce que llegaron y no las ciento ochenta y siete.
|
|
55
|
+
*
|
|
56
|
+
* Los faltantes van SIEMPRE al final, suba o baje el orden. Es lo que hace
|
|
57
|
+
* cualquier planilla, y la razón es que un vacío no es «el más chico»: es la
|
|
58
|
+
* ausencia de respuesta, y ponerlo primero al invertir el orden llena la primera
|
|
59
|
+
* pantalla con las filas que menos dicen.
|
|
60
|
+
*/
|
|
61
|
+
export declare function sortItems<T>(items: readonly T[], fields: readonly SortField<T>[], sort: SortState | null | undefined): readonly T[];
|
|
62
|
+
export declare function SortMenu({ fields, sort, onSortChange, surface }: SortMenuProps): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useUiCopy } from "../../foundations/copy.js";
|
|
4
|
+
import { Menu } from "../../primitives/menu/Menu.js";
|
|
5
|
+
import { ArrowDownUp, ArrowDownWideNarrow, ArrowUpNarrowWide } from "lucide-react";
|
|
6
|
+
/**
|
|
7
|
+
* Las filas, ordenadas acá.
|
|
8
|
+
*
|
|
9
|
+
* Sólo cuando el campo activo declara `value`; si no, se devuelven tal cual —
|
|
10
|
+
* ordenar una página de un conjunto que se pagina afuera produce un orden que es
|
|
11
|
+
* mentira, porque ordena las doce que llegaron y no las ciento ochenta y siete.
|
|
12
|
+
*
|
|
13
|
+
* Los faltantes van SIEMPRE al final, suba o baje el orden. Es lo que hace
|
|
14
|
+
* cualquier planilla, y la razón es que un vacío no es «el más chico»: es la
|
|
15
|
+
* ausencia de respuesta, y ponerlo primero al invertir el orden llena la primera
|
|
16
|
+
* pantalla con las filas que menos dicen.
|
|
17
|
+
*/
|
|
18
|
+
export function sortItems(items, fields, sort) {
|
|
19
|
+
if (sort === null || sort === undefined)
|
|
20
|
+
return items;
|
|
21
|
+
const field = fields.find((one) => one.field === sort.key);
|
|
22
|
+
if (field?.value === undefined)
|
|
23
|
+
return items;
|
|
24
|
+
const read = field.value;
|
|
25
|
+
const sign = sort.direction === "asc" ? 1 : -1;
|
|
26
|
+
return [...items].sort((a, b) => {
|
|
27
|
+
const left = read(a);
|
|
28
|
+
const right = read(b);
|
|
29
|
+
const leftMissing = left === null || left === undefined || left === "";
|
|
30
|
+
const rightMissing = right === null || right === undefined || right === "";
|
|
31
|
+
if (leftMissing && rightMissing)
|
|
32
|
+
return 0;
|
|
33
|
+
if (leftMissing)
|
|
34
|
+
return 1;
|
|
35
|
+
if (rightMissing)
|
|
36
|
+
return -1;
|
|
37
|
+
if (typeof left === "number" && typeof right === "number")
|
|
38
|
+
return (left - right) * sign;
|
|
39
|
+
return String(left).localeCompare(String(right)) * sign;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
export function SortMenu({ fields, sort, onSortChange, surface }) {
|
|
43
|
+
const copy = useUiCopy();
|
|
44
|
+
if (fields.length === 0)
|
|
45
|
+
return null;
|
|
46
|
+
const current = fields.find((one) => one.field === sort?.key);
|
|
47
|
+
const groups = [
|
|
48
|
+
{
|
|
49
|
+
label: copy.sorting.by,
|
|
50
|
+
options: fields.map((one) => ({
|
|
51
|
+
id: one.field,
|
|
52
|
+
label: one.label,
|
|
53
|
+
checked: one.field === sort?.key,
|
|
54
|
+
})),
|
|
55
|
+
},
|
|
56
|
+
/* La dirección sólo cuando hay algo ordenado: «descendente» sin campo no
|
|
57
|
+
ordena nada, y una opción que no hace nada es una opción que se prueba. */
|
|
58
|
+
...(current === undefined
|
|
59
|
+
? []
|
|
60
|
+
: [
|
|
61
|
+
{
|
|
62
|
+
options: [
|
|
63
|
+
{
|
|
64
|
+
id: "__asc__",
|
|
65
|
+
label: current.ascLabel ?? copy.sorting.asc,
|
|
66
|
+
icon: ArrowUpNarrowWide,
|
|
67
|
+
checked: sort?.direction === "asc",
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: "__desc__",
|
|
71
|
+
label: current.descLabel ?? copy.sorting.desc,
|
|
72
|
+
icon: ArrowDownWideNarrow,
|
|
73
|
+
checked: sort?.direction === "desc",
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
},
|
|
77
|
+
{ options: [{ id: "__none__", label: copy.sorting.clear }] },
|
|
78
|
+
]),
|
|
79
|
+
];
|
|
80
|
+
return (_jsx(Menu, { label: current === undefined ? copy.sorting.label : current.label, icon: sort === null || sort === undefined
|
|
81
|
+
? ArrowDownUp
|
|
82
|
+
: sort.direction === "asc"
|
|
83
|
+
? ArrowUpNarrowWide
|
|
84
|
+
: ArrowDownWideNarrow, groups: groups, active: sort !== null && sort !== undefined, size: "sm", width: "sm", closeOnSelect: false, ...(surface === undefined ? {} : { surface }), onSelect: (id) => {
|
|
85
|
+
if (id === "__none__")
|
|
86
|
+
return onSortChange(null);
|
|
87
|
+
if (id === "__asc__") {
|
|
88
|
+
return onSortChange(sort === null ? null : { ...sort, direction: "asc" });
|
|
89
|
+
}
|
|
90
|
+
if (id === "__desc__") {
|
|
91
|
+
return onSortChange(sort === null ? null : { ...sort, direction: "desc" });
|
|
92
|
+
}
|
|
93
|
+
/* Volver a elegir el campo que ya está puesto DA VUELTA la dirección, que
|
|
94
|
+
es lo que hace el clic en un encabezado. Sin eso, invertir el orden son
|
|
95
|
+
dos gestos en un menú y uno en la tabla — la misma capacidad costando
|
|
96
|
+
distinto según la presentación. */
|
|
97
|
+
if (sort?.key === id) {
|
|
98
|
+
return onSortChange({
|
|
99
|
+
key: id,
|
|
100
|
+
direction: sort.direction === "asc" ? "desc" : "asc",
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
onSortChange({ key: id, direction: "asc" });
|
|
104
|
+
} }));
|
|
105
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Surface } from "../../foundations/types.js";
|
|
2
|
+
import type { LucideIcon } from "lucide-react";
|
|
3
|
+
/** How the same list is shown: table, board, timeline.
|
|
4
|
+
*
|
|
5
|
+
* The icons go together and touching —a segmented group and not three loose
|
|
6
|
+
* buttons— because they are MUTUALLY EXCLUSIVE options of a single question.
|
|
7
|
+
* Three separate buttons read as three different actions, and then it is not
|
|
8
|
+
* understood that choosing one turns the other off.
|
|
9
|
+
*
|
|
10
|
+
* The label goes in the `title` and in the `aria-label`, never drawn: they are
|
|
11
|
+
* three silhouettes that are learned once and recognized afterwards without
|
|
12
|
+
* reading, and writing them out would burn the width the search box needs. */
|
|
13
|
+
export interface ViewOption {
|
|
14
|
+
id: string;
|
|
15
|
+
label: string;
|
|
16
|
+
icon: LucideIcon;
|
|
17
|
+
}
|
|
18
|
+
export declare function ViewSwitcher({ options, current, onSelect, surface, }: {
|
|
19
|
+
options: readonly ViewOption[];
|
|
20
|
+
current: string;
|
|
21
|
+
onSelect?: (id: string) => void;
|
|
22
|
+
/** By default it inherits it from the surface that wraps it. */
|
|
23
|
+
surface?: Surface;
|
|
24
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useUiCopy } from "../../foundations/copy.js";
|
|
4
|
+
import { bySurface, useSurface } from "../../foundations/surface.js";
|
|
5
|
+
export function ViewSwitcher({ options, current, onSelect, surface, }) {
|
|
6
|
+
const activeSurface = useSurface(surface);
|
|
7
|
+
const copy = useUiCopy();
|
|
8
|
+
const rule = bySurface(activeSurface, "border-line-strong", "border-chrome-ink/45");
|
|
9
|
+
return (_jsx("div", { role: "group", "aria-label": copy.present.group, className: `flex shrink-0 items-center overflow-hidden rounded-control border ${rule}`, children: options.map((o, i) => {
|
|
10
|
+
const here = o.id === current;
|
|
11
|
+
const Icon = o.icon;
|
|
12
|
+
return (_jsx("button", { type: "button", onClick: () => onSelect?.(o.id), "aria-pressed": here, title: o.label, "aria-label": o.label, className: `inline-flex size-(--size-control-sm) items-center justify-center transition-colors duration-(--duration-instant) focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-primary ${i === 0 ? "" : `border-l ${rule}`} ${here
|
|
13
|
+
? bySurface(activeSurface, "bg-ink/8 text-ink", "bg-chrome-link/20 text-chrome-link")
|
|
14
|
+
: bySurface(activeSurface, "bg-surface text-muted hover:bg-ink/5 hover:text-ink", "bg-chrome-ink/6 text-chrome-muted hover:bg-chrome-ink/14 hover:text-chrome-ink")}`, children: _jsx(Icon, { className: "size-(--size-icon-md)", strokeWidth: 1.75 }) }, o.id));
|
|
15
|
+
}) }));
|
|
16
|
+
}
|