@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,20 @@
|
|
|
1
|
+
import type { ColumnControl, ColumnSpec } from "./columns.js";
|
|
2
|
+
import type { Surface } from "../../foundations/types.js";
|
|
3
|
+
/**
|
|
4
|
+
* The Odoo columns submenu: required fields stay checked and cannot be
|
|
5
|
+
* unchecked; optional fields toggle. It stays open while toggling so hiding
|
|
6
|
+
* three columns is three clicks, not three open-close round trips.
|
|
7
|
+
*
|
|
8
|
+
* Drawn only when the set declared optional columns. A menu of required-only
|
|
9
|
+
* fields would be a list of things you cannot do.
|
|
10
|
+
*/
|
|
11
|
+
export declare function ColumnsMenu({ control, surface, }: {
|
|
12
|
+
control: ColumnControl;
|
|
13
|
+
surface?: Surface;
|
|
14
|
+
}): import("react").JSX.Element | null;
|
|
15
|
+
export declare function specsFromColumns(columns: readonly {
|
|
16
|
+
id: string;
|
|
17
|
+
header: string;
|
|
18
|
+
optional?: boolean;
|
|
19
|
+
defaultHidden?: boolean;
|
|
20
|
+
}[], fallbackOptional: boolean): ColumnSpec[];
|
|
@@ -0,0 +1,46 @@
|
|
|
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 { Columns3 } from "lucide-react";
|
|
6
|
+
/**
|
|
7
|
+
* The Odoo columns submenu: required fields stay checked and cannot be
|
|
8
|
+
* unchecked; optional fields toggle. It stays open while toggling so hiding
|
|
9
|
+
* three columns is three clicks, not three open-close round trips.
|
|
10
|
+
*
|
|
11
|
+
* Drawn only when the set declared optional columns. A menu of required-only
|
|
12
|
+
* fields would be a list of things you cannot do.
|
|
13
|
+
*/
|
|
14
|
+
export function ColumnsMenu({ control, surface, }) {
|
|
15
|
+
const copy = useUiCopy();
|
|
16
|
+
if (!control.hasOptional)
|
|
17
|
+
return null;
|
|
18
|
+
const requiredOptions = control.required.map((column) => ({
|
|
19
|
+
id: column.id,
|
|
20
|
+
label: column.label,
|
|
21
|
+
checked: true,
|
|
22
|
+
disabled: true,
|
|
23
|
+
hint: copy.frame.required,
|
|
24
|
+
}));
|
|
25
|
+
const optionalOptions = control.optional.map((column) => ({
|
|
26
|
+
id: column.id,
|
|
27
|
+
label: column.label,
|
|
28
|
+
checked: control.isVisible(column.id),
|
|
29
|
+
}));
|
|
30
|
+
const groups = requiredOptions.length === 0
|
|
31
|
+
? [{ label: copy.frame.show, options: optionalOptions }]
|
|
32
|
+
: [
|
|
33
|
+
{ label: copy.frame.required, options: requiredOptions },
|
|
34
|
+
{ label: copy.frame.optional, options: optionalOptions },
|
|
35
|
+
];
|
|
36
|
+
const hiddenOptional = control.optional.some((column) => !control.isVisible(column.id));
|
|
37
|
+
return (_jsx(Menu, { label: copy.frame.columns, icon: Columns3, groups: groups, onSelect: control.toggle, closeOnSelect: false, active: hiddenOptional, ...(surface === undefined ? {} : { surface }), size: "sm" }));
|
|
38
|
+
}
|
|
39
|
+
export function specsFromColumns(columns, fallbackOptional) {
|
|
40
|
+
return columns.map((column) => ({
|
|
41
|
+
id: column.id,
|
|
42
|
+
label: column.header,
|
|
43
|
+
optional: column.optional ?? fallbackOptional,
|
|
44
|
+
...(column.defaultHidden === true ? { defaultHidden: true } : {}),
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { GroupingControl } from "./grouping.js";
|
|
2
|
+
import { type GroupableField, type GroupGrain } from "../../foundations/grouping.js";
|
|
3
|
+
/**
|
|
4
|
+
* «AGRUPAR POR», adentro del panel de recortes.
|
|
5
|
+
*
|
|
6
|
+
* Context: va en la MISMA columna donde están los filtros y los favoritos, y no
|
|
7
|
+
* en un control aparte. Filtrar y agrupar son la misma pregunta —«qué parte de
|
|
8
|
+
* esto estoy mirando»— y separarlos obliga a abrir dos cosas para armar un
|
|
9
|
+
* recorte, sin poder ver los dos estados a la vez.
|
|
10
|
+
*
|
|
11
|
+
* ── Una fecha se DESPLIEGA, no se elige ─────────────────────────────
|
|
12
|
+
*
|
|
13
|
+
* Un campo común es una opción: se tilda y listo. Un campo de fecha no, porque
|
|
14
|
+
* «agrupar por fecha» no quiere decir nada hasta decir con qué corte — y los
|
|
15
|
+
* cortes no son excluyentes: «por mes y adentro por día» es una pregunta
|
|
16
|
+
* legítima y frecuente. Así que una fecha se abre y ofrece sus granos como
|
|
17
|
+
* CASILLAS, que se tildan de a varias.
|
|
18
|
+
*
|
|
19
|
+
* ── Y se ordenan solas ──────────────────────────────────────────────
|
|
20
|
+
*
|
|
21
|
+
* Tildar «día» y después «mes» da «mes › día», nunca al revés. No es una
|
|
22
|
+
* preferencia: un día entero vive adentro de un solo mes, así que partir primero
|
|
23
|
+
* por día deja un nivel de mes que no parte nada. La dirección la pone el
|
|
24
|
+
* calendario y por eso la pone el sistema — ver `normalizeLevels`.
|
|
25
|
+
*/
|
|
26
|
+
export type GroupingLevelsProps = {
|
|
27
|
+
/** Por qué campos se puede partir. Sin ninguno no se dibuja nada: una columna
|
|
28
|
+
* vacía en el panel es peor que una columna que no está. */
|
|
29
|
+
fields: readonly GroupableField[];
|
|
30
|
+
control: GroupingControl;
|
|
31
|
+
/** Cómo se llama cada grano, cuando el producto lo dice con otras palabras.
|
|
32
|
+
* Sin esto, las de la copia en el idioma activo. */
|
|
33
|
+
grainLabel?: (grain: GroupGrain) => string;
|
|
34
|
+
};
|
|
35
|
+
export declare function GroupingLevels({ fields, control, grainLabel }: GroupingLevelsProps): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { grainName } from "../../foundations/calendar.js";
|
|
4
|
+
import { useUiCopy } from "../../foundations/copy.js";
|
|
5
|
+
import { GRAIN_ORDER, levelKey, } from "../../foundations/grouping.js";
|
|
6
|
+
import { ChevronDown, ChevronRight, ChevronUp, X } from "lucide-react";
|
|
7
|
+
import { useState } from "react";
|
|
8
|
+
function fieldLabel(fields, field) {
|
|
9
|
+
return fields.find((one) => one.field === field)?.label ?? field;
|
|
10
|
+
}
|
|
11
|
+
export function GroupingLevels({ fields, control, grainLabel }) {
|
|
12
|
+
const copy = useUiCopy();
|
|
13
|
+
/* Qué fechas están desplegadas. Arranca desplegada la que ya tenga algún grano
|
|
14
|
+
puesto: si no, lo puesto queda escondido detrás de un chevron y hay que
|
|
15
|
+
adivinar dónde. */
|
|
16
|
+
const [opened, setOpened] = useState(() => control.levels.filter((one) => one.grain !== undefined).map((one) => one.field));
|
|
17
|
+
if (fields.length === 0)
|
|
18
|
+
return null;
|
|
19
|
+
const placed = control.levels;
|
|
20
|
+
const grain = (one) => grainLabel?.(one) ?? grainName(one);
|
|
21
|
+
const isOpen = (field) => opened.includes(field);
|
|
22
|
+
const toggleOpen = (field) => setOpened((current) => current.includes(field) ? current.filter((one) => one !== field) : [...current, field]);
|
|
23
|
+
return (_jsxs("div", { className: "flex flex-col gap-0.5", children: [placed.length === 0 ? null : (_jsxs(_Fragment, { children: [_jsx("ol", { className: "flex flex-wrap gap-1 px-1 pb-1.5", children: placed.map((level, index) => (_jsx("li", { children: _jsxs("span", { className: "inline-flex items-center gap-1 rounded-pill border border-accent/40 bg-accent/18 py-0.5 pr-0.5 pl-1.5 text-micro text-ink", children: [_jsx("span", { className: "tabular-nums opacity-70", children: index + 1 }), _jsxs("span", { className: "max-w-32 truncate", children: [fieldLabel(fields, level.field), level.grain === undefined ? "" : ` · ${grain(level.grain)}`] }), _jsx("button", { type: "button", onClick: () => control.move(level.field, -1), disabled: index === 0, "aria-label": copy.grouping.up, title: copy.grouping.up, className: "rounded-pill p-0.5 hover:bg-primary/15 disabled:opacity-30 disabled:hover:bg-transparent", children: _jsx(ChevronUp, { className: "size-3", strokeWidth: 2.5 }) }), _jsx("button", { type: "button", onClick: () => control.move(level.field, 1), disabled: index === placed.length - 1, "aria-label": copy.grouping.down, title: copy.grouping.down, className: "rounded-pill p-0.5 hover:bg-primary/15 disabled:opacity-30 disabled:hover:bg-transparent", children: _jsx(ChevronDown, { className: "size-3", strokeWidth: 2.5 }) }), _jsx("button", { type: "button", onClick: () => control.remove(level.field, level.grain), "aria-label": copy.grouping.remove, title: copy.grouping.remove, className: "rounded-pill p-0.5 hover:bg-primary/15", children: _jsx(X, { className: "size-3", strokeWidth: 2.5 }) })] }) }, levelKey(level)))) }), _jsx("div", { className: "mb-1 h-px bg-line", role: "separator" })] })), fields.map((one) => {
|
|
24
|
+
const grains = one.grains;
|
|
25
|
+
/* Un campo común: una casilla y nada más. */
|
|
26
|
+
if (grains === undefined || grains.length === 0) {
|
|
27
|
+
const on = control.has(one.field);
|
|
28
|
+
return (_jsxs("button", { type: "button", onClick: () => control.toggle(one.field), "aria-pressed": on, className: `flex items-center gap-2 rounded-control px-1.5 py-1 text-left transition-colors duration-(--duration-instant) hover:bg-primary/8 ${on ? "bg-primary/6" : ""}`, children: [_jsx(Mark, { on: on }), _jsx("span", { className: `min-w-0 truncate text-meta ${on ? "font-medium text-ink" : "text-ink"}`, children: one.label })] }, one.field));
|
|
29
|
+
}
|
|
30
|
+
/* Una FECHA: se despliega y ofrece sus cortes como casillas. */
|
|
31
|
+
const open = isOpen(one.field);
|
|
32
|
+
const puestos = placed.filter((level) => level.field === one.field);
|
|
33
|
+
return (_jsxs("div", { className: "flex flex-col", children: [_jsxs("button", { type: "button", onClick: () => toggleOpen(one.field), "aria-expanded": open, className: "flex items-center gap-1.5 rounded-control px-1.5 py-1 text-left transition-colors duration-(--duration-instant) hover:bg-primary/8", children: [_jsx(ChevronRight, { className: `size-(--size-icon-sm) shrink-0 text-muted transition-transform duration-(--duration-instant) ${open ? "rotate-90" : ""}`, strokeWidth: 2, "aria-hidden": true }), _jsx("span", { className: `min-w-0 flex-1 truncate text-meta ${puestos.length > 0 ? "font-medium text-ink" : "text-ink"}`, children: one.label }), puestos.length === 0 ? null : (_jsx("span", { className: "inline-flex shrink-0 items-center rounded-pill border border-accent/40 bg-accent/18 px-2 py-0.5 text-micro tabular-nums text-ink", children: puestos.length }))] }), !open ? null : (_jsx("div", { className: "flex flex-col pl-(--spacing-indent)", children: GRAIN_ORDER.filter((g) => grains.includes(g)).map((g) => {
|
|
34
|
+
const on = control.has(one.field, g);
|
|
35
|
+
return (_jsxs("button", { type: "button", onClick: () => control.toggle(one.field, g), "aria-pressed": on, className: `flex items-center gap-2 rounded-control px-1.5 py-1 text-left transition-colors duration-(--duration-instant) hover:bg-primary/8 ${on ? "bg-primary/6" : ""}`, children: [_jsx(Mark, { on: on }), _jsx("span", { className: `min-w-0 truncate text-meta ${on ? "font-medium text-ink" : "text-muted"}`, children: grain(g) })] }, g));
|
|
36
|
+
}) }))] }, one.field));
|
|
37
|
+
}), placed.length === 0 ? null : (_jsxs(_Fragment, { children: [_jsx("div", { className: "my-1 h-px bg-line", role: "separator" }), _jsx("button", { type: "button", onClick: () => control.clear(), className: "rounded-control px-2 py-1 text-left text-meta text-muted transition-colors duration-(--duration-instant) hover:bg-ink/4 hover:text-ink", children: copy.grouping.clear })] }))] }));
|
|
38
|
+
}
|
|
39
|
+
/** La marca de tildado. La columna existe SIEMPRE, tildada o no: sin ella,
|
|
40
|
+
* tildar corre el texto y la lista salta. Apagada se dibuja el aro, la misma
|
|
41
|
+
* marca que el panel de filtros, para que las dos columnas se lean como
|
|
42
|
+
* casillas y no como texto suelto con un hueco a la izquierda. */
|
|
43
|
+
function Mark({ on }) {
|
|
44
|
+
return (_jsx("span", { className: "flex size-(--size-icon-sm) shrink-0 items-center justify-center", children: on ? (_jsx("span", { className: "size-1.5 rounded-pill bg-primary" })) : (_jsx("span", { className: "size-3 rounded-pill border border-line-strong" })) }));
|
|
45
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Surface } from "../../foundations/types.js";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
export declare function Listing({ bar, metrics, sticky, surface, children, }: {
|
|
4
|
+
/** The `ListingBar`: actions, title, search, pagination and views. */
|
|
5
|
+
bar?: ReactNode;
|
|
6
|
+
/**
|
|
7
|
+
* La barra se queda arriba al scrollear, debajo de lo que ya esté pegado.
|
|
8
|
+
*
|
|
9
|
+
* Es opt-in y no el default porque el fondo tiene que ser el de lo que hay
|
|
10
|
+
* DETRÁS, y eso depende de dónde esté puesta: el arquetipo de lista vive
|
|
11
|
+
* sobre el piso de la página, pero un `FrameView` —«el vistazo» de un
|
|
12
|
+
* registro— vive sobre el PAPEL blanco, y ahí la banda color piso se ve como
|
|
13
|
+
* una mancha. Se descubrió comparando historias, no razonando.
|
|
14
|
+
*/
|
|
15
|
+
sticky?: boolean;
|
|
16
|
+
/** Sobre qué está puesta la lista. Sólo se usa para el fondo de la banda que
|
|
17
|
+
* se pega: `chrome` pinta el mismo azul que ya pinta la barra —o sea,
|
|
18
|
+
* invisible— y cualquier otra pinta el PISO, que es lo que hay detrás de una
|
|
19
|
+
* lista de página. Las que viven adentro de algo blanco —un cajón, una vista
|
|
20
|
+
* previa— son compactas, y en compacto la barra no se pega. */
|
|
21
|
+
surface?: Surface;
|
|
22
|
+
/** The metrics that also narrow down. They go BELOW the bar and above the
|
|
23
|
+
* rows: they belong to the set I am looking at, not to the frame that frames
|
|
24
|
+
* it — above the bar they read as part of the navigation. */
|
|
25
|
+
metrics?: ReactNode;
|
|
26
|
+
children: ReactNode;
|
|
27
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useHeaderSlot } from "../shell/headerSlot.js";
|
|
4
|
+
import { byDensity, useDensity } from "../../foundations/density.js";
|
|
5
|
+
import { useStickyLayer } from "../../foundations/sticky.js";
|
|
6
|
+
import { createPortal } from "react-dom";
|
|
7
|
+
/* ==================================================================== *
|
|
8
|
+
* LISTING — the shape of a screen of N records. A single one, for all of them.
|
|
9
|
+
*
|
|
10
|
+
* The request was literal: «in every list window don't forget that it has
|
|
11
|
+
* [Search (Filters)] [List]». Up to here every view built that by hand —one
|
|
12
|
+
* put the totals on top, another the search box below the upload panel, another
|
|
13
|
+
* had no search box at all— and the result is that two lists of the same
|
|
14
|
+
* product get used differently.
|
|
15
|
+
*
|
|
16
|
+
* Four bands, always in the same order:
|
|
17
|
+
*
|
|
18
|
+
* bar crumbs · search · pagination
|
|
19
|
+
* actions · title · columns · views
|
|
20
|
+
* body the rows, in a card with a border
|
|
21
|
+
*
|
|
22
|
+
* ── The list goes in a card and the background stays behind ─────────
|
|
23
|
+
*
|
|
24
|
+
* It is what separates an application from a document. Without the card the
|
|
25
|
+
* rows float over nothing and the screen reads as running text; the card puts
|
|
26
|
+
* a boundary around the set, and that boundary is what makes it read as «one
|
|
27
|
+
* thing with elements inside».
|
|
28
|
+
*
|
|
29
|
+
* ── Two densities, and both are needed ──────────────────────────────
|
|
30
|
+
*
|
|
31
|
+
* `table` aligned, sortable columns. It wins when there is width: the data
|
|
32
|
+
* gets compared across rows without reading, which is what a table
|
|
33
|
+
* exists for.
|
|
34
|
+
* `rows` two lines per record, big name and metadata underneath. It wins
|
|
35
|
+
* when there is NO width —a narrow panel, the preview— because a
|
|
36
|
+
* six-column table in 380 px is not a table, it is an accordion.
|
|
37
|
+
*
|
|
38
|
+
* It is not an aesthetic preference: it is the available width. That is why
|
|
39
|
+
* the consumer passes it and this piece does not choose it — only whoever
|
|
40
|
+
* composes knows which surface it is going to land on.
|
|
41
|
+
*
|
|
42
|
+
* ── The row lays itself out; nobody tells it how ────────────────────
|
|
43
|
+
*
|
|
44
|
+
* WHICH presentation is the consumer's call. How a row arranges itself inside
|
|
45
|
+
* the one that was chosen is nobody's: there is no width at which a row «turns
|
|
46
|
+
* narrow», and no breakpoint written anywhere. Every zone asks for what it
|
|
47
|
+
* needs —the name `--container-name`, the tags `--container-tags`, the
|
|
48
|
+
* metadata what it says— and the line does what a line does: what does not fit
|
|
49
|
+
* wraps, and the name keeps the first one.
|
|
50
|
+
*
|
|
51
|
+
* **That is why it holds up with a checkbox, an icon, actions, no tags, or a
|
|
52
|
+
* metadata three times longer.** None of that is declared anywhere: they all
|
|
53
|
+
* take their width from the same line, so a list that selects wraps a little
|
|
54
|
+
* earlier than one that does not, on its own. Asking the density instead —a
|
|
55
|
+
* decision per SURFACE— got it wrong in the one place it mattered: a list in a
|
|
56
|
+
* 480 px drawer is comfortable —it draws its title, its counters, its full
|
|
57
|
+
* padding— and narrow at the same time, and the name was left with whatever
|
|
58
|
+
* was over between a metadata that did not shrink and a tag column of 8 rem:
|
|
59
|
+
* about a dozen pixels, «ve…».
|
|
60
|
+
* ==================================================================== */
|
|
61
|
+
export function Listing({ bar, metrics, sticky = false, surface, children, }) {
|
|
62
|
+
const d = useDensity();
|
|
63
|
+
/* La barra se PEGA arriba y las filas se van debajo de ella.
|
|
64
|
+
Context: con 187 filas, scrollear para ver la última dejaba sin buscador,
|
|
65
|
+
sin paginador y sin el conmutador de presentación — o sea que recortar
|
|
66
|
+
después de mirar obligaba a subir de nuevo. Se pega debajo de lo que ya
|
|
67
|
+
esté pegado (la barra de la aplicación) y empuja la pila para el
|
|
68
|
+
encabezado de la tabla, que se pega debajo de ella. Ver `system/sticky.tsx`. */
|
|
69
|
+
const band = useStickyLayer();
|
|
70
|
+
/* En compacto NO se pega, y no es por falta de espacio: compacto es un cajón
|
|
71
|
+
o una vista previa, o sea una caja corta con su propia cabecera y su propio
|
|
72
|
+
scroll. Una banda pegada ahí se pinta encima del papel blanco del cajón —se
|
|
73
|
+
ve como una mancha— para resolver un scroll que en cinco filas no existe.
|
|
74
|
+
Es la misma razón por la que `ListingBar` tampoco dibuja la identidad ahí. */
|
|
75
|
+
const pegada = sticky && d === "comfortable";
|
|
76
|
+
/* With a sidebar the bar is the screen's header: it goes in the frame's full-width slot,
|
|
77
|
+
above the sidebar (see `shell/headerSlot.ts`), and the frame does the sticking. */
|
|
78
|
+
const slot = useHeaderSlot();
|
|
79
|
+
const inSlot = pegada && slot !== null;
|
|
80
|
+
return (
|
|
81
|
+
/* Context: the box of rows carries no border. What separates it from the
|
|
82
|
+
background is its white and its radius; a frame around a list that is
|
|
83
|
+
already inside a framed panel is a nested rectangle, and nested
|
|
84
|
+
rectangles are what make a product read like a cloud console. */
|
|
85
|
+
_jsxs("div", { className: `flex min-w-0 flex-col ${byDensity(d, "gap-2.5", "gap-2")}`, children: [bar === undefined || bar === null ? null : inSlot ? (createPortal(bar, slot)) : (_jsx("div", { ref: band.ref, className: `min-w-0 ${pegada ? `sticky z-30 ${surface === "chrome" ? "bg-chrome" : "bg-ground"}` : ""}`, style: pegada ? band.self : undefined, children: bar })), inSlot && metrics !== undefined ? _jsx("div", { className: "pt-2", children: metrics }) : metrics, _jsx("div", { className: "min-w-0 overflow-clip rounded-box bg-surface", style: pegada && !inSlot ? band.below : undefined, children: children })] }));
|
|
86
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { Surface } from "../../foundations/types.js";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
/** THE BAR — two rows, two questions.
|
|
4
|
+
*
|
|
5
|
+
* ┌──────────┬──────────────────────────────┬──────────────┐
|
|
6
|
+
* │ crumbs │ 🔍 search… ⚙3 │ 1–8 of 187 ‹ › │
|
|
7
|
+
* ├──────────┼──────────────────────────────┼──────────────┤
|
|
8
|
+
* │ [New] │ Facturas · 180 · 7 · 8 │ [columns] [▤][▦] │
|
|
9
|
+
* └──────────┴──────────────────────────────┴──────────────┘
|
|
10
|
+
* WHERE / HOW MUCH I SEE HOW I LOOK
|
|
11
|
+
*
|
|
12
|
+
* Pagination answers how much of the filtered set is on screen, so it stays
|
|
13
|
+
* with the search. Columns and the presentation switcher answer how the set
|
|
14
|
+
* is looked at, so they sit with the actions — not next to the page range.
|
|
15
|
+
* Putting the four of them on the search line is how the field and «1–80»
|
|
16
|
+
* painted over each other the moment the window shrank.
|
|
17
|
+
*
|
|
18
|
+
* ── Why it is two lines tall ────────────────────────────────────────
|
|
19
|
+
*
|
|
20
|
+
* Because the three things that go on the left do not fit in one: a
|
|
21
|
+
* comfortable button is 34 px tall, a list title may have to wrap onto two
|
|
22
|
+
* lines, and below it goes a sentence saying what the list is made of. Forcing
|
|
23
|
+
* it into a single line means shrinking the button —the defect we already
|
|
24
|
+
* fixed— or truncating the title, which is worse: a list that cannot be named
|
|
25
|
+
* in full is indistinguishable from the one next to it.
|
|
26
|
+
*
|
|
27
|
+
* ── The selection transforms the bar, it does not add one ───────────
|
|
28
|
+
*
|
|
29
|
+
* When rows get selected, the identity zone turns into «3 elegidas ✕» plus the
|
|
30
|
+
* actions that apply. **In the same place and without pushing anything.** A
|
|
31
|
+
* bar that appears shifts the list downwards right when somebody is ticking
|
|
32
|
+
* checkboxes, and the row they were about to tick is no longer where it was.
|
|
33
|
+
*
|
|
34
|
+
* The actions on the left do NOT go away: «Nuevo» stays available with three
|
|
35
|
+
* rows selected, because selecting is not entering a mode.
|
|
36
|
+
*
|
|
37
|
+
* ── Inside a record it is ONE line (`inline`) ────────────────────────
|
|
38
|
+
*
|
|
39
|
+
* ┌───────────────────────────────────────────────────────────────┐
|
|
40
|
+
* │ 🔍 search the lines… 12 records [sort][cols][▤▦] │
|
|
41
|
+
* └───────────────────────────────────────────────────────────────┘
|
|
42
|
+
* WHAT I LOOK FOR (fills) HOW MANY HOW I LOOK
|
|
43
|
+
*
|
|
44
|
+
* A collection that is a FIELD of a record — the lines of an order, the
|
|
45
|
+
* contacts of a customer, the journal items of an invoice — is not a screen.
|
|
46
|
+
* Everything the two rows above answer is already answered by the record
|
|
47
|
+
* around it: where I am (the record's crumbs), what this is (the tab it sits
|
|
48
|
+
* in), what I can do (the record's actions). Drawn with the page bar, it
|
|
49
|
+
* spent two rows and a rule on the one question left —the search, alone and
|
|
50
|
+
* centred on the first row; «Ordenar» and the switcher alone on the second—
|
|
51
|
+
* and pushed the rows, which are what the tab is for, below the fold.
|
|
52
|
+
*
|
|
53
|
+
* So it is a single line, and the order is the order of the questions: the
|
|
54
|
+
* search FILLS from the left (it is the only thing with variable content),
|
|
55
|
+
* then how many there are, then how they are looked at, then what starts a
|
|
56
|
+
* new one. No band, no padding, no hairline: it sits on the record's paper,
|
|
57
|
+
* aligned with the fields above it, and a light band inside a white sheet is
|
|
58
|
+
* a rectangle inside a rectangle.
|
|
59
|
+
*
|
|
60
|
+
* On a phone the line breaks by itself, with no threshold written anywhere:
|
|
61
|
+
* the search asks for `--container-name` before it gives up its place, so
|
|
62
|
+
* when the controls do not fit next to it they drop to a second line, pushed
|
|
63
|
+
* to the right, and the search keeps the whole first one.
|
|
64
|
+
*
|
|
65
|
+
* The count is a live region: typing in the search changes it, and somebody
|
|
66
|
+
* who cannot see the rows thin out hears how many are left. With
|
|
67
|
+
* `pagination` the page range replaces it — it already says how many. */
|
|
68
|
+
export declare function ListingBar({ actions, title, description, counters, selection, search, pagination, views, crumbs, surface, inline, count, }: {
|
|
69
|
+
/** What starts something new. It stays visible even when there is a selection. */
|
|
70
|
+
actions?: ReactNode;
|
|
71
|
+
title?: string;
|
|
72
|
+
/** One sentence: what this list is made of, or what can be done with it. It
|
|
73
|
+
* goes below the title and that is why it can be a real sentence. */
|
|
74
|
+
description?: string;
|
|
75
|
+
/** The totals per category. The ones in the pagination say how many there
|
|
76
|
+
* are; these say what they are made of, which is another question. */
|
|
77
|
+
counters?: readonly {
|
|
78
|
+
label: string;
|
|
79
|
+
value: number | string;
|
|
80
|
+
}[];
|
|
81
|
+
/** When there are selected rows. It replaces the identity, in the same place. */
|
|
82
|
+
selection?: {
|
|
83
|
+
count: number;
|
|
84
|
+
onClear?: () => void;
|
|
85
|
+
actions?: ReactNode;
|
|
86
|
+
};
|
|
87
|
+
search?: ReactNode;
|
|
88
|
+
pagination?: ReactNode;
|
|
89
|
+
/** Columns menu and presentation switcher. They sit on the actions row, on
|
|
90
|
+
* the right: they are how I look at the set, not how many of it I see. */
|
|
91
|
+
views?: ReactNode;
|
|
92
|
+
/** Where I come from. It goes above the row and only on the chrome. */
|
|
93
|
+
crumbs?: ReactNode;
|
|
94
|
+
/** `chrome` paints the whole band navy and everything inside inherits it:
|
|
95
|
+
* the search box, the pagination and the switcher. See `system/surface.tsx`. */
|
|
96
|
+
surface?: Surface;
|
|
97
|
+
/** One line, for a collection inside a record: search, count, views, actions. The
|
|
98
|
+
* title, the description, the counters and the crumbs are not drawn — the record
|
|
99
|
+
* around it already says them. Always on the canvas. See the header. */
|
|
100
|
+
inline?: boolean;
|
|
101
|
+
/** How many records there are, said on the inline line when there is no
|
|
102
|
+
* `pagination` to say it. Ignored by the two-row bar: its identity has counters. */
|
|
103
|
+
count?: number;
|
|
104
|
+
}): import("react").JSX.Element;
|
|
105
|
+
export type ListIdentity = {
|
|
106
|
+
title?: string;
|
|
107
|
+
description?: string;
|
|
108
|
+
counters?: readonly {
|
|
109
|
+
label: string;
|
|
110
|
+
value: number | string;
|
|
111
|
+
}[];
|
|
112
|
+
};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { fillCopy, useUiCopy } from "../../foundations/copy.js";
|
|
4
|
+
import { byDensity, useDensity } from "../../foundations/density.js";
|
|
5
|
+
import { SurfaceProvider, useSurface } from "../../foundations/surface.js";
|
|
6
|
+
import { Line } from "../../primitives/block/Line.js";
|
|
7
|
+
import { X } from "lucide-react";
|
|
8
|
+
/** THE BAR — two rows, two questions.
|
|
9
|
+
*
|
|
10
|
+
* ┌──────────┬──────────────────────────────┬──────────────┐
|
|
11
|
+
* │ crumbs │ 🔍 search… ⚙3 │ 1–8 of 187 ‹ › │
|
|
12
|
+
* ├──────────┼──────────────────────────────┼──────────────┤
|
|
13
|
+
* │ [New] │ Facturas · 180 · 7 · 8 │ [columns] [▤][▦] │
|
|
14
|
+
* └──────────┴──────────────────────────────┴──────────────┘
|
|
15
|
+
* WHERE / HOW MUCH I SEE HOW I LOOK
|
|
16
|
+
*
|
|
17
|
+
* Pagination answers how much of the filtered set is on screen, so it stays
|
|
18
|
+
* with the search. Columns and the presentation switcher answer how the set
|
|
19
|
+
* is looked at, so they sit with the actions — not next to the page range.
|
|
20
|
+
* Putting the four of them on the search line is how the field and «1–80»
|
|
21
|
+
* painted over each other the moment the window shrank.
|
|
22
|
+
*
|
|
23
|
+
* ── Why it is two lines tall ────────────────────────────────────────
|
|
24
|
+
*
|
|
25
|
+
* Because the three things that go on the left do not fit in one: a
|
|
26
|
+
* comfortable button is 34 px tall, a list title may have to wrap onto two
|
|
27
|
+
* lines, and below it goes a sentence saying what the list is made of. Forcing
|
|
28
|
+
* it into a single line means shrinking the button —the defect we already
|
|
29
|
+
* fixed— or truncating the title, which is worse: a list that cannot be named
|
|
30
|
+
* in full is indistinguishable from the one next to it.
|
|
31
|
+
*
|
|
32
|
+
* ── The selection transforms the bar, it does not add one ───────────
|
|
33
|
+
*
|
|
34
|
+
* When rows get selected, the identity zone turns into «3 elegidas ✕» plus the
|
|
35
|
+
* actions that apply. **In the same place and without pushing anything.** A
|
|
36
|
+
* bar that appears shifts the list downwards right when somebody is ticking
|
|
37
|
+
* checkboxes, and the row they were about to tick is no longer where it was.
|
|
38
|
+
*
|
|
39
|
+
* The actions on the left do NOT go away: «Nuevo» stays available with three
|
|
40
|
+
* rows selected, because selecting is not entering a mode.
|
|
41
|
+
*
|
|
42
|
+
* ── Inside a record it is ONE line (`inline`) ────────────────────────
|
|
43
|
+
*
|
|
44
|
+
* ┌───────────────────────────────────────────────────────────────┐
|
|
45
|
+
* │ 🔍 search the lines… 12 records [sort][cols][▤▦] │
|
|
46
|
+
* └───────────────────────────────────────────────────────────────┘
|
|
47
|
+
* WHAT I LOOK FOR (fills) HOW MANY HOW I LOOK
|
|
48
|
+
*
|
|
49
|
+
* A collection that is a FIELD of a record — the lines of an order, the
|
|
50
|
+
* contacts of a customer, the journal items of an invoice — is not a screen.
|
|
51
|
+
* Everything the two rows above answer is already answered by the record
|
|
52
|
+
* around it: where I am (the record's crumbs), what this is (the tab it sits
|
|
53
|
+
* in), what I can do (the record's actions). Drawn with the page bar, it
|
|
54
|
+
* spent two rows and a rule on the one question left —the search, alone and
|
|
55
|
+
* centred on the first row; «Ordenar» and the switcher alone on the second—
|
|
56
|
+
* and pushed the rows, which are what the tab is for, below the fold.
|
|
57
|
+
*
|
|
58
|
+
* So it is a single line, and the order is the order of the questions: the
|
|
59
|
+
* search FILLS from the left (it is the only thing with variable content),
|
|
60
|
+
* then how many there are, then how they are looked at, then what starts a
|
|
61
|
+
* new one. No band, no padding, no hairline: it sits on the record's paper,
|
|
62
|
+
* aligned with the fields above it, and a light band inside a white sheet is
|
|
63
|
+
* a rectangle inside a rectangle.
|
|
64
|
+
*
|
|
65
|
+
* On a phone the line breaks by itself, with no threshold written anywhere:
|
|
66
|
+
* the search asks for `--container-name` before it gives up its place, so
|
|
67
|
+
* when the controls do not fit next to it they drop to a second line, pushed
|
|
68
|
+
* to the right, and the search keeps the whole first one.
|
|
69
|
+
*
|
|
70
|
+
* The count is a live region: typing in the search changes it, and somebody
|
|
71
|
+
* who cannot see the rows thin out hears how many are left. With
|
|
72
|
+
* `pagination` the page range replaces it — it already says how many. */
|
|
73
|
+
export function ListingBar({ actions, title, description, counters, selection, search, pagination, views, crumbs, surface, inline = false, count, }) {
|
|
74
|
+
const selecting = selection !== undefined && selection.count > 0;
|
|
75
|
+
const d = useDensity();
|
|
76
|
+
const copy = useUiCopy();
|
|
77
|
+
const activeSurface = useSurface(inline ? "canvas" : surface);
|
|
78
|
+
const isChrome = activeSurface === "chrome";
|
|
79
|
+
/* In compact the title and the description are NOT drawn, and it is not for
|
|
80
|
+
lack of room: in a preview or on a phone, the surface header already says
|
|
81
|
+
what I am looking at. Repeating it here burns the only line there is for
|
|
82
|
+
the search box — which is what is actually needed. */
|
|
83
|
+
const withIdentity = d === "comfortable";
|
|
84
|
+
/* Two rows, and the split is the same one as in a record:
|
|
85
|
+
1 · WHERE I AM and HOW MANY THERE ARE — crumbs, search, page range.
|
|
86
|
+
2 · WHAT I CAN DO and HOW I LOOK — actions, identity, columns / views.
|
|
87
|
+
|
|
88
|
+
Views used to sit next to the pagination. On a narrow window the search
|
|
89
|
+
field and «1–80 / 211» painted over each other; columns and the switcher
|
|
90
|
+
were the extra weight on a line that only needed how MANY. */
|
|
91
|
+
const center = selecting ? (_jsxs("div", { className: "flex min-w-0 items-center gap-x-2", children: [_jsxs("span", { className: `inline-flex h-(--size-control-md) shrink-0 items-center gap-1.5 rounded-control pr-1 pl-2.5 text-meta font-medium ${isChrome ? "bg-chrome-ink/14 text-chrome-ink" : "bg-ink/8 text-ink"}`, children: [_jsx("span", { className: "tabular-nums", children: selection?.count.toLocaleString(copy.locale) }), copy.selection.chosen, _jsx("button", { type: "button", onClick: selection?.onClear, "aria-label": copy.selection.clear, title: copy.selection.clear, className: `inline-flex size-5 items-center justify-center rounded-pill transition-colors duration-(--duration-instant) ${isChrome ? "hover:bg-chrome-ink/20" : "hover:bg-primary/20"}`, children: _jsx(X, { className: "size-3", strokeWidth: 2.5 }) })] }), selection?.actions === undefined ? null : (_jsx("div", { className: "flex min-w-0 items-center gap-1.5", children: selection.actions }))] })) : search === undefined ? null : (
|
|
92
|
+
/* Selecting replaces the search box: what gets selected is over what is
|
|
93
|
+
ALREADY filtered, so searching there would change the set underneath the
|
|
94
|
+
selection. */
|
|
95
|
+
_jsx("div", { className: "w-full min-w-0", children: search }));
|
|
96
|
+
if (inline) {
|
|
97
|
+
const howMany = pagination ??
|
|
98
|
+
(count === undefined ? null : (_jsx("span", { role: "status", className: "shrink-0 text-meta text-muted tabular-nums", children: count === 1
|
|
99
|
+
? copy.list.countOne
|
|
100
|
+
: fillCopy(copy.list.count, { count: count.toLocaleString(copy.locale) }) })));
|
|
101
|
+
return (_jsx(SurfaceProvider, { value: "canvas", children: _jsxs("div", { className: `flex min-w-0 flex-wrap items-center gap-y-1.5 ${byDensity(d, "gap-x-3", "gap-x-2")}`, children: [center === null ? null : (_jsx("div", { className: "flex min-w-0 flex-1 basis-(--container-name) items-center overflow-x-clip overflow-y-visible", children: center })), howMany, views === undefined ? null : (_jsx("div", { className: "ml-auto flex shrink-0 items-center gap-2", children: views })), actions === undefined ? null : (_jsx("div", { className: `flex shrink-0 items-center gap-1.5 ${views === undefined ? "ml-auto" : ""}`, children: actions }))] }) }));
|
|
102
|
+
}
|
|
103
|
+
/* El campo de búsqueda va en el CENTRO de la línea, y centrado contra la
|
|
104
|
+
línea: pide `--container-search` y los dos lados se reparten lo que sobra
|
|
105
|
+
en mitades iguales. Antes tomaba el resto, así que un rastro largo a la
|
|
106
|
+
izquierda lo corría —y dos listas seguidas dibujaban su buscador en dos
|
|
107
|
+
lugares distintos—. Ver `primitives/block/Line.tsx`. */
|
|
108
|
+
const topRow = (_jsx(Line, { asks: "var(--container-crumb)", centerAsks: center === null ? "auto" : "var(--container-search)", gap: byDensity(d, "gap-x-4 gap-y-2", "gap-x-2 gap-y-1.5"), align: "items-start", start: crumbs,
|
|
109
|
+
/* `overflow-x-clip` y NO `overflow-hidden`: lo que hay que recortar es el
|
|
110
|
+
ancho —que el campo no pinte encima de «1–80»—, y `hidden` en un eje
|
|
111
|
+
fuerza `auto` en el otro, así que también recortaba hacia ABAJO. El
|
|
112
|
+
panel de filtros del `SearchBar` cuelga con `absolute top-full`, o sea
|
|
113
|
+
justo afuera de esta caja: apretar el embudo abría un panel que nadie
|
|
114
|
+
veía. `clip` es el único valor que deja `visible` en el otro eje. */
|
|
115
|
+
center: center === null ? null : (_jsx("div", { className: "flex w-full min-w-0 items-center overflow-x-clip overflow-y-visible", children: center })), end: pagination }));
|
|
116
|
+
const bottomRow = actions === undefined && !withIdentity && views === undefined ? null : (
|
|
117
|
+
/* On a narrow list (a phone, a drawer) the name and its counters take the whole first
|
|
118
|
+
line and the action and the controls go below: squeezed between them, the
|
|
119
|
+
description wrapped one word per line. */
|
|
120
|
+
_jsxs("div", { className: `flex min-w-0 flex-wrap items-center gap-y-2 ${byDensity(d, "gap-x-4", "gap-x-2")}`, children: [actions === undefined ? null : (_jsx("div", { className: "flex shrink-0 items-center gap-1.5", children: actions })), !withIdentity ? null : (_jsxs("div", { className: "flex min-w-0 flex-1 flex-col @max-2xl:order-first @max-2xl:basis-full", children: [title === undefined ? null : (
|
|
121
|
+
/* No `truncate`: the title may wrap onto two lines. A list that
|
|
122
|
+
cannot be named in full is indistinguishable from the one next
|
|
123
|
+
to it. */
|
|
124
|
+
/* `title` and semibold: the name of the list is the entry point of the
|
|
125
|
+
screen. At `lead` it was smaller than the metrics under it, so the
|
|
126
|
+
largest thing on a list page was a number (measured: 1.46:1 over the
|
|
127
|
+
body, below the ~1.5 an eye needs to find where to start). */
|
|
128
|
+
_jsx("h2", { className: `text-title leading-tight font-semibold ${isChrome ? "text-chrome-ink" : "text-ink"}`, children: title })), description === undefined && counters === undefined ? null : (_jsxs("div", { className: "mt-0.5 flex min-w-0 flex-wrap items-baseline gap-x-3 gap-y-0.5", children: [counters?.map((c) => (_jsxs("span", { className: "flex items-baseline gap-1", children: [_jsx("span", { className: `text-meta font-medium tabular-nums ${isChrome ? "text-chrome-ink" : "text-ink"}`, children: typeof c.value === "number"
|
|
129
|
+
? c.value.toLocaleString(copy.locale)
|
|
130
|
+
: c.value }), _jsx("span", { className: `text-micro ${isChrome ? "text-chrome-muted" : "text-muted"}`, children: c.label })] }, c.label))), description === undefined ? null : (_jsx("span", { className: `min-w-0 text-micro ${isChrome ? "text-chrome-muted" : "text-muted"}`, children: description }))] }))] })), views === undefined ? null : (_jsx("div", { className: "ml-auto flex shrink-0 items-center gap-2", children: views }))] }));
|
|
131
|
+
const bar = (_jsxs("div", { className: `flex min-w-0 flex-col ${byDensity(d, "gap-2", "gap-1.5")}`, children: [!isChrome && bottomRow !== null ? (_jsx("div", { className: "border-b border-line pb-2", children: topRow })) : (topRow), bottomRow] }));
|
|
132
|
+
/* On the canvas the bar is a light control band: white, a hairline below, the same
|
|
133
|
+
padding as the dark one — a list inside an application whose bar is already dark keeps
|
|
134
|
+
ONE dark band per screen. Without its own band the bar sat flush on the page and its
|
|
135
|
+
controls had nothing to belong to. */
|
|
136
|
+
if (!isChrome) {
|
|
137
|
+
return (_jsx(SurfaceProvider, { value: "canvas", children: _jsx("div", { className: `flex min-w-0 flex-col border-b border-line bg-surface ${byDensity(d, "px-3.5 py-2.5", "px-2.5 py-2")}`, children: bar }) }));
|
|
138
|
+
}
|
|
139
|
+
/* On the chrome the bar IS the band: the background reaches the edges and
|
|
140
|
+
everything inside inherits the surface, so there is no way to forget to
|
|
141
|
+
pass it — which is how the status bar ended up with the canvas palette on
|
|
142
|
+
top of the navy. */
|
|
143
|
+
return (_jsx(SurfaceProvider, { value: "chrome", children: _jsx("div", { className: `flex min-w-0 flex-col bg-chrome ${byDensity(d, "px-3.5 py-2.5", "px-2.5 py-2")}`, children: bar }) }));
|
|
144
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Surface } from "../../foundations/types.js";
|
|
2
|
+
/** How many of how many, and how I move to the next page.
|
|
3
|
+
*
|
|
4
|
+
* **It lives ONCE and it is mounted in two places.** The normal destination is
|
|
5
|
+
* the search bar: you search, you narrow down and you paginate on the same
|
|
6
|
+
* line, without looking down to the footer to discover there are another
|
|
7
|
+
* hundred and sixty. The footer (`ListingFooter`) existed only for the lists
|
|
8
|
+
* that do NOT have a search box — there is no narrowing line there to put it on.
|
|
9
|
+
*
|
|
10
|
+
* Two implementations of one pagination diverge at the first fix, so this is
|
|
11
|
+
* the only one and both places receive it.
|
|
12
|
+
*
|
|
13
|
+
* ── The range, not the page number ──────────────────────────────────
|
|
14
|
+
*
|
|
15
|
+
* «1–25 de 187» answers three questions —where I am, how much I see, how much
|
|
16
|
+
* there is— and «page 1 of 8» answers a single one, in units that depend on a
|
|
17
|
+
* page size the user did not choose.
|
|
18
|
+
*
|
|
19
|
+
* ── Why the page size is visible ────────────────────────────────────
|
|
20
|
+
*
|
|
21
|
+
* Because it is the difference between scrolling eight times and scrolling
|
|
22
|
+
* once. In a tool where somebody compares a hundred and eighty invoices,
|
|
23
|
+
* leaving it fixed at 25 forces paginating over something that is not a
|
|
24
|
+
* product decision. */
|
|
25
|
+
export declare function Pagination({ from, to, total, pageSize, pageSizes, onPageSizeChange, onPrev, onNext, surface, }: {
|
|
26
|
+
from: number;
|
|
27
|
+
to: number;
|
|
28
|
+
total: number;
|
|
29
|
+
pageSize?: number;
|
|
30
|
+
pageSizes?: readonly number[];
|
|
31
|
+
onPageSizeChange?: (next: number) => void;
|
|
32
|
+
onPrev?: () => void;
|
|
33
|
+
onNext?: () => void;
|
|
34
|
+
/** By default it inherits it from the surface that wraps it. */
|
|
35
|
+
surface?: Surface;
|
|
36
|
+
}): import("react").JSX.Element;
|