@sincpro/web-ui 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +157 -2
- package/dist/components/assist/AssistMark.d.ts +64 -0
- package/dist/components/assist/AssistMark.js +108 -0
- package/dist/components/assist/AssistPending.d.ts +24 -0
- package/dist/components/assist/AssistPending.js +26 -0
- package/dist/components/assist/AssistSuggestion.d.ts +42 -0
- package/dist/components/assist/AssistSuggestion.js +55 -0
- package/dist/components/assist/ChangeReview.d.ts +56 -0
- package/dist/components/assist/ChangeReview.js +121 -0
- package/dist/components/assist/assist.d.ts +77 -0
- package/dist/components/assist/assist.js +87 -0
- package/dist/components/assist/index.d.ts +5 -0
- package/dist/components/assist/index.js +7 -0
- package/dist/components/chatter/Chatter.d.ts +161 -0
- package/dist/components/chatter/Chatter.js +232 -0
- package/dist/components/chatter/Messages.d.ts +66 -0
- package/dist/components/chatter/Messages.js +43 -0
- package/dist/components/chatter/Timeline.d.ts +21 -0
- package/dist/components/chatter/Timeline.js +30 -0
- package/dist/components/chatter/index.d.ts +3 -0
- package/dist/components/chatter/index.js +4 -0
- package/dist/components/form/Form.d.ts +52 -0
- package/dist/components/form/Form.js +49 -0
- package/dist/components/form/Sheet.d.ts +43 -0
- package/dist/components/form/Sheet.js +83 -0
- package/dist/components/form/index.d.ts +2 -0
- package/dist/components/form/index.js +4 -0
- package/dist/components/frame/Chart.d.ts +13 -0
- package/dist/components/frame/Chart.js +24 -0
- package/dist/components/frame/FrameView.d.ts +33 -0
- package/dist/components/frame/FrameView.js +125 -0
- package/dist/components/frame/index.d.ts +3 -0
- package/dist/components/frame/index.js +3 -0
- package/dist/components/frame/model.d.ts +43 -0
- package/dist/components/frame/model.js +101 -0
- package/dist/components/index.d.ts +10 -0
- package/dist/components/index.js +11 -0
- package/dist/components/lines/LineItems.d.ts +74 -0
- package/dist/components/lines/LineItems.js +229 -0
- package/dist/components/lines/index.d.ts +1 -0
- package/dist/components/lines/index.js +2 -0
- package/dist/components/lines/lines.d.ts +23 -0
- package/dist/components/lines/lines.js +26 -0
- package/dist/components/listing/ColumnsMenu.d.ts +20 -0
- package/dist/components/listing/ColumnsMenu.js +46 -0
- package/dist/components/listing/GroupingLevels.d.ts +35 -0
- package/dist/components/listing/GroupingLevels.js +45 -0
- package/dist/components/listing/Listing.d.ts +27 -0
- package/dist/components/listing/Listing.js +86 -0
- package/dist/components/listing/ListingBar.d.ts +73 -0
- package/dist/components/listing/ListingBar.js +105 -0
- package/dist/components/listing/Pagination.d.ts +36 -0
- package/dist/components/listing/Pagination.js +69 -0
- package/dist/components/listing/RecordCard.d.ts +33 -0
- package/dist/components/listing/RecordCard.js +22 -0
- package/dist/components/listing/RecordLink.d.ts +7 -0
- package/dist/components/listing/RecordLink.js +22 -0
- package/dist/components/listing/Row.d.ts +106 -0
- package/dist/components/listing/Row.js +122 -0
- package/dist/components/listing/SortMenu.d.ts +62 -0
- package/dist/components/listing/SortMenu.js +105 -0
- package/dist/components/listing/ViewSwitcher.d.ts +24 -0
- package/dist/components/listing/ViewSwitcher.js +16 -0
- package/dist/components/listing/columns.d.ts +47 -0
- package/dist/components/listing/columns.js +72 -0
- package/dist/components/listing/grouping.d.ts +35 -0
- package/dist/components/listing/grouping.js +47 -0
- package/dist/components/listing/index.d.ts +17 -0
- package/dist/components/listing/index.js +20 -0
- package/dist/components/listing/modelColumns.d.ts +50 -0
- package/dist/components/listing/modelColumns.js +103 -0
- package/dist/components/listing/pages.d.ts +78 -0
- package/dist/components/listing/pages.js +135 -0
- package/dist/components/listing/selection.d.ts +21 -0
- package/dist/components/listing/selection.js +43 -0
- package/dist/components/listing/set.d.ts +48 -0
- package/dist/components/listing/set.js +24 -0
- package/dist/components/listing/tree.d.ts +28 -0
- package/dist/components/listing/tree.js +23 -0
- package/dist/components/navigation/CommandPalette.d.ts +40 -0
- package/dist/components/navigation/CommandPalette.js +114 -0
- package/dist/components/navigation/NavigationTree.d.ts +31 -0
- package/dist/components/navigation/NavigationTree.js +79 -0
- package/dist/components/navigation/commands.d.ts +30 -0
- package/dist/components/navigation/commands.js +30 -0
- package/dist/components/navigation/index.d.ts +3 -0
- package/dist/components/navigation/index.js +4 -0
- package/dist/components/record/Breadcrumb.d.ts +42 -0
- package/dist/components/record/Breadcrumb.js +98 -0
- package/dist/components/record/DirtyActions.d.ts +26 -0
- package/dist/components/record/DirtyActions.js +22 -0
- package/dist/components/record/Pager.d.ts +18 -0
- package/dist/components/record/Pager.js +33 -0
- package/dist/components/record/RecordHeader.d.ts +71 -0
- package/dist/components/record/RecordHeader.js +72 -0
- package/dist/components/record/RecordSkeleton.d.ts +3 -0
- package/dist/components/record/RecordSkeleton.js +21 -0
- package/dist/components/record/SmartButtons.d.ts +35 -0
- package/dist/components/record/SmartButtons.js +38 -0
- package/dist/components/record/StatusBar.d.ts +51 -0
- package/dist/components/record/StatusBar.js +168 -0
- package/dist/components/record/ThumbBar.d.ts +36 -0
- package/dist/components/record/ThumbBar.js +86 -0
- package/dist/components/record/index.d.ts +6 -0
- package/dist/components/record/index.js +8 -0
- package/dist/components/search/CriteriaBuilder.d.ts +58 -0
- package/dist/components/search/CriteriaBuilder.js +278 -0
- package/dist/components/search/CriteriaChips.d.ts +22 -0
- package/dist/components/search/CriteriaChips.js +64 -0
- package/dist/components/search/FilterBuilder.d.ts +142 -0
- package/dist/components/search/FilterBuilder.js +421 -0
- package/dist/components/search/JoinWord.d.ts +8 -0
- package/dist/components/search/JoinWord.js +47 -0
- package/dist/components/search/JsonEditor.d.ts +19 -0
- package/dist/components/search/JsonEditor.js +78 -0
- package/dist/components/search/SearchBar.d.ts +78 -0
- package/dist/components/search/SearchBar.js +387 -0
- package/dist/components/search/criteria.d.ts +116 -0
- package/dist/components/search/criteria.js +159 -0
- package/dist/components/search/datePeriods.d.ts +122 -0
- package/dist/components/search/datePeriods.js +134 -0
- package/dist/components/search/dateRanges.d.ts +54 -0
- package/dist/components/search/dateRanges.js +89 -0
- package/dist/components/search/index.d.ts +9 -0
- package/dist/components/search/index.js +20 -0
- package/dist/components/search/model.d.ts +54 -0
- package/dist/components/search/model.js +37 -0
- package/dist/components/search/query.d.ts +166 -0
- package/dist/components/search/query.js +436 -0
- package/dist/components/shell/AppFrame.d.ts +14 -0
- package/dist/components/shell/AppFrame.js +80 -0
- package/dist/components/shell/TopBar.d.ts +73 -0
- package/dist/components/shell/TopBar.js +54 -0
- package/dist/components/shell/headerSlot.d.ts +17 -0
- package/dist/components/shell/headerSlot.js +21 -0
- package/dist/components/shell/index.d.ts +2 -0
- package/dist/components/shell/index.js +3 -0
- package/dist/foundations/aria.d.ts +14 -0
- package/dist/foundations/aria.js +18 -0
- package/dist/foundations/calendar.d.ts +28 -0
- package/dist/foundations/calendar.js +183 -0
- package/dist/foundations/copy.d.ts +619 -0
- package/dist/foundations/copy.es.d.ts +2 -0
- package/dist/foundations/copy.es.js +377 -0
- package/dist/foundations/copy.js +472 -0
- package/dist/foundations/density.d.ts +16 -0
- package/dist/foundations/density.js +21 -0
- package/dist/foundations/grouping.d.ts +281 -0
- package/dist/foundations/grouping.js +0 -0
- package/dist/foundations/index.d.ts +14 -0
- package/dist/foundations/index.js +30 -0
- package/dist/foundations/layout.d.ts +9 -0
- package/dist/foundations/layout.js +9 -0
- package/dist/foundations/pivot.d.ts +222 -0
- package/dist/foundations/pivot.js +198 -0
- package/dist/foundations/pivotSheet.d.ts +61 -0
- package/dist/foundations/pivotSheet.js +0 -0
- package/dist/foundations/slots.d.ts +67 -0
- package/dist/foundations/slots.js +63 -0
- package/dist/foundations/sticky.d.ts +53 -0
- package/dist/foundations/sticky.js +150 -0
- package/dist/foundations/surface.d.ts +9 -0
- package/dist/foundations/surface.js +26 -0
- package/dist/foundations/theme.d.ts +42 -0
- package/dist/foundations/theme.js +88 -0
- package/dist/foundations/types.d.ts +23 -0
- package/dist/foundations/types.js +50 -0
- package/dist/foundations/variants.d.ts +19 -0
- package/dist/foundations/variants.js +48 -0
- package/dist/foundations/xlsx.d.ts +86 -0
- package/dist/foundations/xlsx.js +0 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +19 -0
- package/dist/primitives/block/Block.d.ts +66 -0
- package/dist/primitives/block/Block.js +34 -0
- package/dist/primitives/block/Line.d.ts +18 -0
- package/dist/primitives/block/Line.js +45 -0
- package/dist/primitives/block/SectionGrid.d.ts +30 -0
- package/dist/primitives/block/SectionGrid.js +29 -0
- package/dist/primitives/block/Steps.d.ts +76 -0
- package/dist/primitives/block/Steps.js +43 -0
- package/dist/primitives/block/index.d.ts +4 -0
- package/dist/primitives/block/index.js +7 -0
- package/dist/primitives/button/Button.d.ts +137 -0
- package/dist/primitives/button/Button.js +234 -0
- package/dist/primitives/button/index.d.ts +1 -0
- package/dist/primitives/button/index.js +2 -0
- package/dist/primitives/cell/CellStack.d.ts +47 -0
- package/dist/primitives/cell/CellStack.js +33 -0
- package/dist/primitives/cell/index.d.ts +1 -0
- package/dist/primitives/cell/index.js +3 -0
- package/dist/primitives/choice/ComboBox.d.ts +16 -0
- package/dist/primitives/choice/ComboBox.js +52 -0
- package/dist/primitives/choice/RadioGroup.d.ts +18 -0
- package/dist/primitives/choice/RadioGroup.js +50 -0
- package/dist/primitives/choice/SearchField.d.ts +24 -0
- package/dist/primitives/choice/SearchField.js +27 -0
- package/dist/primitives/choice/SegmentedControl.d.ts +16 -0
- package/dist/primitives/choice/SegmentedControl.js +69 -0
- package/dist/primitives/choice/Slider.d.ts +16 -0
- package/dist/primitives/choice/Slider.js +46 -0
- package/dist/primitives/choice/Switch.d.ts +14 -0
- package/dist/primitives/choice/Switch.js +65 -0
- package/dist/primitives/choice/ThemeSwitcher.d.ts +10 -0
- package/dist/primitives/choice/ThemeSwitcher.js +25 -0
- package/dist/primitives/choice/index.d.ts +8 -0
- package/dist/primitives/choice/index.js +11 -0
- package/dist/primitives/choice/options.d.ts +28 -0
- package/dist/primitives/choice/options.js +6 -0
- package/dist/primitives/date/Calendar.d.ts +31 -0
- package/dist/primitives/date/Calendar.js +110 -0
- package/dist/primitives/date/DatePicker.d.ts +34 -0
- package/dist/primitives/date/DatePicker.js +99 -0
- package/dist/primitives/date/index.d.ts +2 -0
- package/dist/primitives/date/index.js +4 -0
- package/dist/primitives/dialog/Dialog.d.ts +118 -0
- package/dist/primitives/dialog/Dialog.js +121 -0
- package/dist/primitives/dialog/index.d.ts +1 -0
- package/dist/primitives/dialog/index.js +2 -0
- package/dist/primitives/disclosure/Disclosure.d.ts +36 -0
- package/dist/primitives/disclosure/Disclosure.js +52 -0
- package/dist/primitives/disclosure/index.d.ts +1 -0
- package/dist/primitives/disclosure/index.js +2 -0
- package/dist/primitives/field/Checks.d.ts +57 -0
- package/dist/primitives/field/Checks.js +72 -0
- package/dist/primitives/field/DropZone.d.ts +42 -0
- package/dist/primitives/field/DropZone.js +52 -0
- package/dist/primitives/field/Field.d.ts +166 -0
- package/dist/primitives/field/Field.js +240 -0
- package/dist/primitives/field/Suggest.d.ts +48 -0
- package/dist/primitives/field/Suggest.js +43 -0
- package/dist/primitives/field/aria.d.ts +61 -0
- package/dist/primitives/field/aria.js +70 -0
- package/dist/primitives/field/context.d.ts +30 -0
- package/dist/primitives/field/context.js +13 -0
- package/dist/primitives/field/index.d.ts +7 -0
- package/dist/primitives/field/index.js +11 -0
- package/dist/primitives/field/layout.d.ts +24 -0
- package/dist/primitives/field/layout.js +10 -0
- package/dist/primitives/field/mode.d.ts +9 -0
- package/dist/primitives/field/mode.js +13 -0
- package/dist/primitives/hint/Hint.d.ts +31 -0
- package/dist/primitives/hint/Hint.js +42 -0
- package/dist/primitives/hint/index.d.ts +1 -0
- package/dist/primitives/hint/index.js +1 -0
- package/dist/primitives/index.d.ts +19 -0
- package/dist/primitives/index.js +20 -0
- package/dist/primitives/menu/Menu.d.ts +100 -0
- package/dist/primitives/menu/Menu.js +59 -0
- package/dist/primitives/menu/index.d.ts +1 -0
- package/dist/primitives/menu/index.js +2 -0
- package/dist/primitives/notice/Callout.d.ts +49 -0
- package/dist/primitives/notice/Callout.js +28 -0
- package/dist/primitives/notice/Notice.d.ts +45 -0
- package/dist/primitives/notice/Notice.js +61 -0
- package/dist/primitives/notice/Toast.d.ts +55 -0
- package/dist/primitives/notice/Toast.js +142 -0
- package/dist/primitives/notice/index.d.ts +3 -0
- package/dist/primitives/notice/index.js +5 -0
- package/dist/primitives/overlay/Popover.d.ts +6 -0
- package/dist/primitives/overlay/Popover.js +27 -0
- package/dist/primitives/overlay/PopoverTrigger.d.ts +55 -0
- package/dist/primitives/overlay/PopoverTrigger.js +38 -0
- package/dist/primitives/overlay/Tooltip.d.ts +38 -0
- package/dist/primitives/overlay/Tooltip.js +74 -0
- package/dist/primitives/overlay/index.d.ts +3 -0
- package/dist/primitives/overlay/index.js +5 -0
- package/dist/primitives/picker/Picker.d.ts +48 -0
- package/dist/primitives/picker/Picker.js +150 -0
- package/dist/primitives/picker/index.d.ts +1 -0
- package/dist/primitives/picker/index.js +2 -0
- package/dist/primitives/surface/Peek.d.ts +60 -0
- package/dist/primitives/surface/Peek.js +71 -0
- package/dist/primitives/surface/index.d.ts +1 -0
- package/dist/primitives/surface/index.js +2 -0
- package/dist/primitives/table/CellEditor.d.ts +14 -0
- package/dist/primitives/table/CellEditor.js +83 -0
- package/dist/primitives/table/DataTable.d.ts +135 -0
- package/dist/primitives/table/DataTable.js +244 -0
- package/dist/primitives/table/EditableTable.d.ts +96 -0
- package/dist/primitives/table/EditableTable.js +423 -0
- package/dist/primitives/table/PivotTable.d.ts +14 -0
- package/dist/primitives/table/PivotTable.js +0 -0
- package/dist/primitives/table/ReportTable.d.ts +38 -0
- package/dist/primitives/table/ReportTable.js +106 -0
- package/dist/primitives/table/RowActions.d.ts +16 -0
- package/dist/primitives/table/RowActions.js +28 -0
- package/dist/primitives/table/SheetTable.d.ts +70 -0
- package/dist/primitives/table/SheetTable.js +197 -0
- package/dist/primitives/table/Table.d.ts +106 -0
- package/dist/primitives/table/Table.js +116 -0
- package/dist/primitives/table/index.d.ts +7 -0
- package/dist/primitives/table/index.js +9 -0
- package/dist/primitives/tabs/Tabs.d.ts +91 -0
- package/dist/primitives/tabs/Tabs.js +56 -0
- package/dist/primitives/tabs/index.d.ts +1 -0
- package/dist/primitives/tabs/index.js +1 -0
- package/dist/primitives/tag/Tag.d.ts +36 -0
- package/dist/primitives/tag/Tag.js +68 -0
- package/dist/primitives/tag/Tags.d.ts +57 -0
- package/dist/primitives/tag/Tags.js +104 -0
- package/dist/primitives/tag/TagsField.d.ts +32 -0
- package/dist/primitives/tag/TagsField.js +182 -0
- package/dist/primitives/tag/choices.d.ts +38 -0
- package/dist/primitives/tag/choices.js +26 -0
- package/dist/primitives/tag/index.d.ts +4 -0
- package/dist/primitives/tag/index.js +8 -0
- package/dist/primitives/toolbar/Toolbar.d.ts +13 -0
- package/dist/primitives/toolbar/Toolbar.js +36 -0
- package/dist/primitives/toolbar/index.d.ts +1 -0
- package/dist/primitives/toolbar/index.js +2 -0
- package/dist/primitives/value/Copyable.d.ts +37 -0
- package/dist/primitives/value/Copyable.js +50 -0
- package/dist/primitives/value/Fecha.d.ts +12 -0
- package/dist/primitives/value/Fecha.js +80 -0
- package/dist/primitives/value/Meter.d.ts +29 -0
- package/dist/primitives/value/Meter.js +46 -0
- package/dist/primitives/value/Metric.d.ts +42 -0
- package/dist/primitives/value/Metric.js +38 -0
- package/dist/primitives/value/Person.d.ts +90 -0
- package/dist/primitives/value/Person.js +208 -0
- package/dist/primitives/value/Relation.d.ts +69 -0
- package/dist/primitives/value/Relation.js +57 -0
- package/dist/primitives/value/Totals.d.ts +26 -0
- package/dist/primitives/value/Totals.js +4 -0
- package/dist/primitives/value/Value.d.ts +77 -0
- package/dist/primitives/value/Value.js +119 -0
- package/dist/primitives/value/index.d.ts +8 -0
- package/dist/primitives/value/index.js +9 -0
- package/dist/tokens.css +1025 -0
- package/dist/views/index.d.ts +2 -0
- package/dist/views/index.js +3 -0
- package/dist/views/list/List.d.ts +113 -0
- package/dist/views/list/List.js +97 -0
- package/dist/views/list/ListView.d.ts +6 -0
- package/dist/views/list/ListView.js +269 -0
- package/dist/views/list/columns.d.ts +18 -0
- package/dist/views/list/columns.js +27 -0
- package/dist/views/list/derive.d.ts +76 -0
- package/dist/views/list/derive.js +181 -0
- package/dist/views/list/index.d.ts +2 -0
- package/dist/views/list/index.js +4 -0
- package/dist/views/list/presentations.d.ts +39 -0
- package/dist/views/list/presentations.js +103 -0
- package/dist/views/list/types.d.ts +272 -0
- package/dist/views/list/types.js +1 -0
- package/dist/views/list/useNarrowList.d.ts +17 -0
- package/dist/views/list/useNarrowList.js +32 -0
- package/dist/views/record/Record.d.ts +126 -0
- package/dist/views/record/Record.js +141 -0
- package/dist/views/record/index.d.ts +1 -0
- package/dist/views/record/index.js +2 -0
- package/package.json +109 -4
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** The commands under each heading, headings in the order they first appear. */
|
|
2
|
+
export function groupCommands(commands) {
|
|
3
|
+
const groups = new Map();
|
|
4
|
+
/* The commands with no heading are listed first, wherever they were written. */
|
|
5
|
+
if (commands.some((c) => c.section === undefined))
|
|
6
|
+
groups.set(undefined, []);
|
|
7
|
+
for (const command of commands) {
|
|
8
|
+
const group = groups.get(command.section);
|
|
9
|
+
if (group === undefined)
|
|
10
|
+
groups.set(command.section, [command]);
|
|
11
|
+
else
|
|
12
|
+
group.push(command);
|
|
13
|
+
}
|
|
14
|
+
return [...groups].map(([section, list]) => ({ section, commands: list }));
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Whether a command answers a query. `contains` is the locale's comparison — React Aria's
|
|
18
|
+
* `useFilter` in the palette — so «facturacion» finds «Facturación» and an uppercase query
|
|
19
|
+
* finds a lowercase label.
|
|
20
|
+
*
|
|
21
|
+
* Every word of the query has to appear, in any order, in the label or the keywords:
|
|
22
|
+
* «nueva fac» finds «Nueva factura», and so does «factura nueva».
|
|
23
|
+
*/
|
|
24
|
+
export function commandMatches(command, query, contains) {
|
|
25
|
+
const words = query.trim().split(/\s+/).filter(Boolean);
|
|
26
|
+
if (words.length === 0)
|
|
27
|
+
return true;
|
|
28
|
+
const haystack = [command.label, ...(command.keywords ?? [])].join(" ");
|
|
29
|
+
return words.every((word) => contains(haystack, word));
|
|
30
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Surface } from "../../foundations/types.js";
|
|
2
|
+
export interface Crumb {
|
|
3
|
+
label: string;
|
|
4
|
+
/** Absent on the last one: the place you are already at cannot be pressed. */
|
|
5
|
+
onClick?: () => void;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Where I came from, without eating the line.
|
|
9
|
+
*
|
|
10
|
+
* Built on React Aria's `Breadcrumbs`: the LAST crumb is the page I am on, so it
|
|
11
|
+
* gets `aria-current="page"` and stops being pressable — React Aria decides that
|
|
12
|
+
* from its position, not from whether the caller forgot an `onClick`. The others
|
|
13
|
+
* are `Link`s, so they press with Enter, show the system focus ring only from the
|
|
14
|
+
* keyboard, and read as links to a screen reader.
|
|
15
|
+
*
|
|
16
|
+
* **The hierarchy is in the TONE, not in a color.** Ancestors are quiet
|
|
17
|
+
* (`text-muted`), the current page is ink and medium weight: the eye lands on where
|
|
18
|
+
* I am and reads backwards. Painting the ancestors in the link blue made the whole
|
|
19
|
+
* row the same weight — four blue words and no «here».
|
|
20
|
+
*
|
|
21
|
+
* Two things keep it inside its place, and they are two because they are two
|
|
22
|
+
* different problems:
|
|
23
|
+
*
|
|
24
|
+
* 1. **Each crumb asks for `--container-crumb` and no more.** A name in this
|
|
25
|
+
* product reaches fifty characters, so one crumb is enough to eat the band even
|
|
26
|
+
* when the trail has three. The rest is cut with «…» and the full name stays in
|
|
27
|
+
* the `title`.
|
|
28
|
+
* 2. **With more than `folded` crumbs, the MIDDLE ones fold** into a «…» menu.
|
|
29
|
+
* By COUNT, not by width: a six-crumb trail is long on any screen. The first
|
|
30
|
+
* —the root, where you go back to— and the last —where I am— never fold, and
|
|
31
|
+
* what folded is still navigable, in order.
|
|
32
|
+
*
|
|
33
|
+
* Folding and truncating are not the same and that is why both exist: one removes
|
|
34
|
+
* CRUMBS and the other removes LETTERS.
|
|
35
|
+
*/
|
|
36
|
+
export declare function Breadcrumb({ crumbs, surface: surfaceProp, folded, }: {
|
|
37
|
+
crumbs: readonly Crumb[];
|
|
38
|
+
surface?: Surface;
|
|
39
|
+
/** From how many crumbs the middle folds. Below this every crumb is drawn:
|
|
40
|
+
* folding two crumbs hides half the trail to save one chevron. */
|
|
41
|
+
folded?: number;
|
|
42
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useUiCopy } from "../../foundations/copy.js";
|
|
4
|
+
import { useSurface } from "../../foundations/surface.js";
|
|
5
|
+
import { focusRing, tv } from "../../foundations/variants.js";
|
|
6
|
+
import { Menu } from "../../primitives/menu/Menu.js";
|
|
7
|
+
import { ChevronRight, MoreHorizontal } from "lucide-react";
|
|
8
|
+
import { Breadcrumb as AriaBreadcrumb, Breadcrumbs, Link } from "react-aria-components";
|
|
9
|
+
/** The three tones of a trail, per surface: where I came from, where I am, and the
|
|
10
|
+
* chevron between them. The chevron is the faintest thing in the row on purpose —
|
|
11
|
+
* it is punctuation, and punctuation louder than the words makes the words hard to
|
|
12
|
+
* read. */
|
|
13
|
+
const crumbStyles = tv({
|
|
14
|
+
extend: focusRing,
|
|
15
|
+
base: "block min-w-0 rounded-mark text-meta transition-colors duration-(--duration-instant)",
|
|
16
|
+
variants: {
|
|
17
|
+
surface: { canvas: "", chrome: "outline-chrome-link" },
|
|
18
|
+
kind: {
|
|
19
|
+
link: "cursor-pointer hover:underline hover:underline-offset-4",
|
|
20
|
+
inert: "cursor-default",
|
|
21
|
+
current: "cursor-default font-medium",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
compoundVariants: [
|
|
25
|
+
{ surface: "canvas", kind: ["link", "inert"], class: "text-muted" },
|
|
26
|
+
{ surface: "canvas", kind: "link", class: "hover:text-ink" },
|
|
27
|
+
{ surface: "canvas", kind: "current", class: "text-ink" },
|
|
28
|
+
{ surface: "chrome", kind: ["link", "inert"], class: "text-chrome-muted" },
|
|
29
|
+
{ surface: "chrome", kind: "link", class: "hover:text-chrome-ink" },
|
|
30
|
+
{ surface: "chrome", kind: "current", class: "text-chrome-ink" },
|
|
31
|
+
],
|
|
32
|
+
});
|
|
33
|
+
/**
|
|
34
|
+
* Where I came from, without eating the line.
|
|
35
|
+
*
|
|
36
|
+
* Built on React Aria's `Breadcrumbs`: the LAST crumb is the page I am on, so it
|
|
37
|
+
* gets `aria-current="page"` and stops being pressable — React Aria decides that
|
|
38
|
+
* from its position, not from whether the caller forgot an `onClick`. The others
|
|
39
|
+
* are `Link`s, so they press with Enter, show the system focus ring only from the
|
|
40
|
+
* keyboard, and read as links to a screen reader.
|
|
41
|
+
*
|
|
42
|
+
* **The hierarchy is in the TONE, not in a color.** Ancestors are quiet
|
|
43
|
+
* (`text-muted`), the current page is ink and medium weight: the eye lands on where
|
|
44
|
+
* I am and reads backwards. Painting the ancestors in the link blue made the whole
|
|
45
|
+
* row the same weight — four blue words and no «here».
|
|
46
|
+
*
|
|
47
|
+
* Two things keep it inside its place, and they are two because they are two
|
|
48
|
+
* different problems:
|
|
49
|
+
*
|
|
50
|
+
* 1. **Each crumb asks for `--container-crumb` and no more.** A name in this
|
|
51
|
+
* product reaches fifty characters, so one crumb is enough to eat the band even
|
|
52
|
+
* when the trail has three. The rest is cut with «…» and the full name stays in
|
|
53
|
+
* the `title`.
|
|
54
|
+
* 2. **With more than `folded` crumbs, the MIDDLE ones fold** into a «…» menu.
|
|
55
|
+
* By COUNT, not by width: a six-crumb trail is long on any screen. The first
|
|
56
|
+
* —the root, where you go back to— and the last —where I am— never fold, and
|
|
57
|
+
* what folded is still navigable, in order.
|
|
58
|
+
*
|
|
59
|
+
* Folding and truncating are not the same and that is why both exist: one removes
|
|
60
|
+
* CRUMBS and the other removes LETTERS.
|
|
61
|
+
*/
|
|
62
|
+
export function Breadcrumb({ crumbs, surface: surfaceProp, folded = 3, }) {
|
|
63
|
+
const copy = useUiCopy();
|
|
64
|
+
const surface = useSurface(surfaceProp);
|
|
65
|
+
const edge = surface === "chrome" ? "text-chrome-muted/50" : "text-line-strong";
|
|
66
|
+
/* The first and the last always; the middle goes to the menu when there are too many. */
|
|
67
|
+
const folds = crumbs.length > folded;
|
|
68
|
+
const hidden = folds ? crumbs.slice(1, -1) : [];
|
|
69
|
+
const shown = folds
|
|
70
|
+
? [crumbs[0], null, crumbs[crumbs.length - 1]]
|
|
71
|
+
: crumbs;
|
|
72
|
+
return (_jsx("nav", { "aria-label": copy.record.location, className: "flex min-w-0", children: _jsx(Breadcrumbs, { "aria-label": copy.record.location, className: "flex min-w-0 flex-wrap items-center gap-1", children: shown.map((crumb, i) => {
|
|
73
|
+
const key = crumb === null ? "folded" : `${i}-${crumb.label}`;
|
|
74
|
+
const last = i === shown.length - 1;
|
|
75
|
+
return (_jsxs(AriaBreadcrumb, { id: key, className: "flex min-w-0 items-center gap-1", children: [i === 0 ? null : (_jsx(ChevronRight, { className: `size-(--size-icon-sm) shrink-0 ${edge}`, strokeWidth: 2, "aria-hidden": true })), crumb === null ? (_jsx(Menu, { label: copy.record.location, title: copy.record.location, icon: MoreHorizontal, iconOnly: true, chevron: false, variant: "text", size: "sm", surface: surface === "chrome" ? "chrome" : "canvas", align: "start", groups: [
|
|
76
|
+
{
|
|
77
|
+
options: hidden.map((one, at) => ({
|
|
78
|
+
id: `crumb-${at}`,
|
|
79
|
+
label: one.label,
|
|
80
|
+
disabled: one.onClick === undefined,
|
|
81
|
+
onSelect: one.onClick,
|
|
82
|
+
})),
|
|
83
|
+
},
|
|
84
|
+
] })) : (_jsx(Link
|
|
85
|
+
/* React Aria already disables the last one; a middle crumb with
|
|
86
|
+
nowhere to go is disabled too, instead of a link that does
|
|
87
|
+
nothing. */
|
|
88
|
+
, {
|
|
89
|
+
/* React Aria already disables the last one; a middle crumb with
|
|
90
|
+
nowhere to go is disabled too, instead of a link that does
|
|
91
|
+
nothing. */
|
|
92
|
+
isDisabled: last ? undefined : crumb.onClick === undefined, onPress: crumb.onClick, className: ({ isFocusVisible }) => crumbStyles({
|
|
93
|
+
surface,
|
|
94
|
+
isFocusVisible,
|
|
95
|
+
kind: last ? "current" : crumb.onClick === undefined ? "inert" : "link",
|
|
96
|
+
}), children: _jsx("span", { className: "block max-w-(--container-crumb) truncate", title: crumb.label, children: crumb.label }) }))] }, key));
|
|
97
|
+
}) }) }));
|
|
98
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface DirtyState {
|
|
2
|
+
onSave: () => void;
|
|
3
|
+
onDiscard: () => void;
|
|
4
|
+
/** It is saving. The button turns off: pressing it twice sends it twice, and
|
|
5
|
+
* when what gets saved is a payment, that matters. */
|
|
6
|
+
saving?: boolean;
|
|
7
|
+
/** What gets saved, when it is not obvious: «guardar la factura», «guardar la
|
|
8
|
+
* regla». */
|
|
9
|
+
label?: string;
|
|
10
|
+
/** Why it CANNOT be saved yet. Present = the button is off and the reason is
|
|
11
|
+
* in its `title` — a button that is off with no reason is a wall. */
|
|
12
|
+
blocked?: string;
|
|
13
|
+
}
|
|
14
|
+
/** Save and discard, in the rail and in place of the action buttons.
|
|
15
|
+
*
|
|
16
|
+
* Context: discard goes to the LEFT of save and with the quieter look. It is
|
|
17
|
+
* the same rule as `FormActions`, and here it matters more: the rail is where
|
|
18
|
+
* the hand is after editing.
|
|
19
|
+
*
|
|
20
|
+
* The «sin guardar» warning is text and not just a color: whoever is leaving
|
|
21
|
+
* the screen has to be able to read what is going on, not deduce it from a
|
|
22
|
+
* dot. */
|
|
23
|
+
export declare function DirtyActions({ onSave, onDiscard, saving, label, blocked, }: DirtyState): import("react").JSX.Element;
|
|
24
|
+
/** The one action a view leads with: the element written with `role="primary"` — the
|
|
25
|
+
* same prop that paints it gold and that `make smoke` counts, so there is no second way to
|
|
26
|
+
* say «this is the main one» that could disagree with the first. */
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useUiCopy } from "../../foundations/copy.js";
|
|
4
|
+
import { bySurface, useSurface } from "../../foundations/surface.js";
|
|
5
|
+
import { Button } from "../../primitives/button/Button.js";
|
|
6
|
+
import { Save, Undo2 } from "lucide-react";
|
|
7
|
+
/** Save and discard, in the rail and in place of the action buttons.
|
|
8
|
+
*
|
|
9
|
+
* Context: discard goes to the LEFT of save and with the quieter look. It is
|
|
10
|
+
* the same rule as `FormActions`, and here it matters more: the rail is where
|
|
11
|
+
* the hand is after editing.
|
|
12
|
+
*
|
|
13
|
+
* The «sin guardar» warning is text and not just a color: whoever is leaving
|
|
14
|
+
* the screen has to be able to read what is going on, not deduce it from a
|
|
15
|
+
* dot. */
|
|
16
|
+
export function DirtyActions({ onSave, onDiscard, saving = false, label, blocked, }) {
|
|
17
|
+
const copy = useUiCopy();
|
|
18
|
+
/* It follows the rail it sits on instead of assuming the dark one: on a light rail the
|
|
19
|
+
chrome palette was white text on white. */
|
|
20
|
+
const surface = useSurface();
|
|
21
|
+
return (_jsxs(_Fragment, { children: [_jsx("span", { className: `inline-flex h-(--size-control-md) shrink-0 items-center gap-1.5 rounded-control px-2.5 text-meta font-medium ${bySurface(surface, "bg-warn/12 text-warn", "bg-chrome-warn/15 text-chrome-warn")}`, children: copy.record.unsaved }), _jsx(Button, { surface: surface, onClick: onDiscard, disabled: saving, icon: Undo2, children: copy.record.discard }), _jsx("span", { ...(blocked === undefined ? {} : { title: blocked }), children: _jsx(Button, { role: "primary", surface: surface, onClick: onSave, disabled: saving || blocked !== undefined, icon: Save, children: saving ? copy.record.saving : (label ?? copy.record.save) }) })] }));
|
|
22
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Surface } from "../../foundations/types.js";
|
|
2
|
+
/** Where I am inside the set I came from, and how I move to the next one.
|
|
3
|
+
*
|
|
4
|
+
* **It turns a record into a position.** Without this, opening the third of a
|
|
5
|
+
* list of eighty and wanting to see the fourth forces going back, finding
|
|
6
|
+
* where you were and opening again — three actions for what is one arrow.
|
|
7
|
+
*
|
|
8
|
+
* It goes far from the actions on purpose: moving between records is not doing
|
|
9
|
+
* something to this record. Stuck next to «Eliminar», one extra click on the
|
|
10
|
+
* wrong arrow is a destructive action. */
|
|
11
|
+
export declare function Pager({ index, total, onPrev, onNext, surface: surfaceProp, }: {
|
|
12
|
+
/** 1-based: what gets shown is «12 de 80», not «11». */
|
|
13
|
+
index: number;
|
|
14
|
+
total: number;
|
|
15
|
+
onPrev?: () => void;
|
|
16
|
+
onNext?: () => void;
|
|
17
|
+
surface?: Surface;
|
|
18
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useUiCopy } from "../../foundations/copy.js";
|
|
4
|
+
import { useSurface } from "../../foundations/surface.js";
|
|
5
|
+
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
6
|
+
/* ---------------------------------------------------------------- *
|
|
7
|
+
* THE PAGER
|
|
8
|
+
* ---------------------------------------------------------------- */
|
|
9
|
+
/** Where I am inside the set I came from, and how I move to the next one.
|
|
10
|
+
*
|
|
11
|
+
* **It turns a record into a position.** Without this, opening the third of a
|
|
12
|
+
* list of eighty and wanting to see the fourth forces going back, finding
|
|
13
|
+
* where you were and opening again — three actions for what is one arrow.
|
|
14
|
+
*
|
|
15
|
+
* It goes far from the actions on purpose: moving between records is not doing
|
|
16
|
+
* something to this record. Stuck next to «Eliminar», one extra click on the
|
|
17
|
+
* wrong arrow is a destructive action. */
|
|
18
|
+
export function Pager({ index, total, onPrev, onNext, surface: surfaceProp, }) {
|
|
19
|
+
const copy = useUiCopy();
|
|
20
|
+
const surface = useSurface(surfaceProp);
|
|
21
|
+
const isChrome = surface === "chrome";
|
|
22
|
+
const faint = isChrome ? "text-chrome-muted" : "text-muted";
|
|
23
|
+
const hover = isChrome
|
|
24
|
+
? "not-disabled:hover:bg-chrome-ink/10 not-disabled:hover:text-chrome-ink"
|
|
25
|
+
: "not-disabled:hover:bg-ink/6 not-disabled:hover:text-ink";
|
|
26
|
+
/* On the chrome the ring is the chrome's link cyan, like the TopBar's: the
|
|
27
|
+
primary navy on a navy band is a ring nobody sees. */
|
|
28
|
+
const ring = isChrome
|
|
29
|
+
? "focus-visible:outline-chrome-link"
|
|
30
|
+
: "focus-visible:outline-primary";
|
|
31
|
+
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 ${ring} ${faint} ${hover}`;
|
|
32
|
+
return (_jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [_jsxs("span", { className: `text-micro tabular-nums ${faint}`, children: [index, " / ", total.toLocaleString(copy.locale)] }), _jsx("button", { type: "button", onClick: onPrev, disabled: index <= 1, "aria-label": copy.record.prev, title: copy.record.prev, className: arrow, children: _jsx(ChevronLeft, { className: "size-(--size-icon-md)", strokeWidth: 2 }) }), _jsx("button", { type: "button", onClick: onNext, disabled: index >= total, "aria-label": copy.record.next, title: copy.record.next, className: arrow, children: _jsx(ChevronRight, { className: "size-(--size-icon-md)", strokeWidth: 2 }) })] }));
|
|
33
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { type Crumb } from "./Breadcrumb.js";
|
|
2
|
+
import { type DirtyState } from "./DirtyActions.js";
|
|
3
|
+
import type { Surface, Tone } from "../../foundations/types.js";
|
|
4
|
+
import type { LucideIcon } from "lucide-react";
|
|
5
|
+
import { type ReactNode } from "react";
|
|
6
|
+
export type { Surface };
|
|
7
|
+
export declare function RecordHeader({ crumbs, pager, actions, status, kicker, icon: Icon, title, subtitle, tag, figure, railSurface, smart, aspects, dirty, chatter, chatterLayout, children, }: {
|
|
8
|
+
crumbs?: readonly Crumb[];
|
|
9
|
+
pager?: ReactNode;
|
|
10
|
+
/** The action buttons. At most three visible + a `Menu` with `iconOnly`, and
|
|
11
|
+
* the rule for choosing which three: the ones that PRODUCE something new,
|
|
12
|
+
* never the navigation ones. They take the rail's surface from context: write
|
|
13
|
+
* them without `surface`, light by default, dark on `Record.Rail surface="chrome"`. */
|
|
14
|
+
actions?: ReactNode;
|
|
15
|
+
/** La historia del registro: `<Chatter …/>`. En `page` es la última fila de
|
|
16
|
+
* la hoja —el mismo papel, separado por una línea— y no una segunda tarjeta.
|
|
17
|
+
* En `aside` sigue al costado, con su propio marco. */
|
|
18
|
+
chatter?: ReactNode;
|
|
19
|
+
/**
|
|
20
|
+
* Dónde se pone respecto de la hoja.
|
|
21
|
+
*
|
|
22
|
+
* page debajo y a todo el ancho, como hoja propia. **El default**: un
|
|
23
|
+
* formulario necesita su ancho, y una columna al costado se lo
|
|
24
|
+
* come en cuanto la pantalla no sobra.
|
|
25
|
+
* aside al costado cuando hay ancho, debajo cuando no.
|
|
26
|
+
*
|
|
27
|
+
* Lo dice el REGISTRO y no se adivina del hijo: la dirección del flex la pone
|
|
28
|
+
* este contenedor, y un padre que mira qué le pasaron adentro para decidir
|
|
29
|
+
* cómo acomodarse se rompe la próxima vez que alguien envuelva el chatter en
|
|
30
|
+
* otra cosa.
|
|
31
|
+
*/
|
|
32
|
+
chatterLayout?: "aside" | "page";
|
|
33
|
+
/** `StatusBar` (where is it at?), the chatter's `Timeline` (what happened to get
|
|
34
|
+
* here?), or both: they are different questions and many records only have one. */
|
|
35
|
+
status?: ReactNode;
|
|
36
|
+
/** The category, small and above the name: «factura de cliente». It makes a
|
|
37
|
+
* name like `NF-02/2026/00137` mean something without deducing it from the
|
|
38
|
+
* format. */
|
|
39
|
+
kicker?: string;
|
|
40
|
+
icon?: LucideIcon;
|
|
41
|
+
title: ReactNode;
|
|
42
|
+
/** The metadata that only DESCRIBES. In mono: the machine wrote it. */
|
|
43
|
+
subtitle?: ReactNode;
|
|
44
|
+
tag?: {
|
|
45
|
+
label: string;
|
|
46
|
+
tone?: Tone;
|
|
47
|
+
};
|
|
48
|
+
/** The record's key figure, as large as the title, on the right. See `RecordIdentity`. */
|
|
49
|
+
figure?: {
|
|
50
|
+
label: string;
|
|
51
|
+
value: ReactNode;
|
|
52
|
+
};
|
|
53
|
+
/** The rail's band. `canvas` (the default) is a light control bar with a hairline below;
|
|
54
|
+
* `chrome` is the dark band, an explicit choice for a record that owns the screen's
|
|
55
|
+
* only dark band (no dark application bar above it). */
|
|
56
|
+
railSurface?: Surface;
|
|
57
|
+
/** The counters that TAKE you somewhere else. They go in the sheet and next
|
|
58
|
+
* to the title — they belong to the record, not to my position in front of
|
|
59
|
+
* it. */
|
|
60
|
+
smart?: ReactNode;
|
|
61
|
+
aspects?: ReactNode;
|
|
62
|
+
/** There are unsaved changes. Its presence REPLACES the action buttons with
|
|
63
|
+
* save/discard, and it is not an aesthetic decision: running «Registrar pago» on
|
|
64
|
+
* an edited and unsaved invoice runs with the old values, and whoever pressed
|
|
65
|
+
* it has no way to know. As long as there are changes, the only thing that
|
|
66
|
+
* can be done is confirm them or throw them away. */
|
|
67
|
+
dirty?: DirtyState;
|
|
68
|
+
/** The record body. It sits IN the paper with the title, so groups, tabs
|
|
69
|
+
* and the page are one white sheet on the ground — Odoo's `<sheet>`. */
|
|
70
|
+
children?: ReactNode;
|
|
71
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { Breadcrumb } from "./Breadcrumb.js";
|
|
4
|
+
import { DirtyActions, } from "./DirtyActions.js";
|
|
5
|
+
import { isPrimary, ThumbBar, thumbPlan } from "./ThumbBar.js";
|
|
6
|
+
import { byDensity, useDensity } from "../../foundations/density.js";
|
|
7
|
+
import { SurfaceProvider } from "../../foundations/surface.js";
|
|
8
|
+
import { Line } from "../../primitives/block/Line.js";
|
|
9
|
+
import { Tag } from "../../primitives/tag/Tag.js";
|
|
10
|
+
import { cloneElement, isValidElement } from "react";
|
|
11
|
+
export function RecordHeader({ crumbs, pager, actions, status, kicker, icon: Icon, title, subtitle, tag, figure, railSurface = "canvas", smart, aspects, dirty, chatter, chatterLayout = "page", children, }) {
|
|
12
|
+
const d = useDensity();
|
|
13
|
+
const hasRail = crumbs !== undefined ||
|
|
14
|
+
pager !== undefined ||
|
|
15
|
+
actions !== undefined ||
|
|
16
|
+
status !== undefined ||
|
|
17
|
+
dirty !== undefined;
|
|
18
|
+
/* In compact the rail goes away ENTIRELY, and it is not for room: in a
|
|
19
|
+
preview, the breadcrumbs and the pager belong to the surface that opened it
|
|
20
|
+
—the list behind is still there— and the actions are already in the
|
|
21
|
+
drawer's header. A rail inside another rail is two bars saying where I am.
|
|
22
|
+
|
|
23
|
+
The state does survive: it moves to the sheet, below the title. It is the
|
|
24
|
+
only thing in the rail that belongs to the RECORD and not to my position in
|
|
25
|
+
front of it. */
|
|
26
|
+
const withRail = hasRail && d === "comfortable";
|
|
27
|
+
/* The thumb bar's content (see THE THUMB BAR in `ThumbBar`): dirty first, else the primary. */
|
|
28
|
+
const { rail: railActions, primaries, mode: thumb, } = thumbPlan({ withRail, actions, dirty });
|
|
29
|
+
/* In compact the TITLE is not drawn either, for the same reason as in
|
|
30
|
+
`Listing`: the surface header already says it. Drawing it here showed it
|
|
31
|
+
twice in the same 480 px drawer — and the name of a customer in this product
|
|
32
|
+
reaches 50 characters, so the repetition eats two whole lines.
|
|
33
|
+
|
|
34
|
+
What does NOT go away: the `tag`, the state, the counters and the aspects.
|
|
35
|
+
They are what the drawer header does not say and the only way to see what
|
|
36
|
+
hangs off it without pinning the panel. */
|
|
37
|
+
const withIdentity = d === "comfortable";
|
|
38
|
+
/* The paper is Odoo's `<sheet>`: white on the ground, hairline, not a
|
|
39
|
+
shadow. It exists on the PAGE, where the chrome rail is a real band
|
|
40
|
+
(crumbs, pager, actions). A drawer is compact so `withRail` is already
|
|
41
|
+
false. A dialog is comfortable but has no crumbs or actions — and it IS
|
|
42
|
+
already a card. Status alone is not a page. */
|
|
43
|
+
const paper = withRail &&
|
|
44
|
+
(crumbs !== undefined ||
|
|
45
|
+
pager !== undefined ||
|
|
46
|
+
actions !== undefined ||
|
|
47
|
+
dirty !== undefined);
|
|
48
|
+
const identity = (_jsxs("div", { className: `flex flex-col ${byDensity(d, "gap-2.5 px-4 pt-3.5", "gap-2 px-0 pt-0")}`, children: [withRail || smart === undefined ? null : _jsx("div", { className: "min-w-0", children: smart }), _jsxs("div", { className: "flex min-w-0 flex-wrap items-start gap-x-5 gap-y-2", children: [_jsxs("div", { className: "flex min-w-0 items-start gap-2.5", children: [Icon === undefined || d === "compact" ? null : (_jsx("span", { className: "mt-1 flex size-9 shrink-0 items-center justify-center rounded-box bg-ink/5 text-muted", children: _jsx(Icon, { className: "size-(--size-icon-lg)", strokeWidth: 1.75 }) })), _jsxs("div", { className: "flex min-w-0 flex-col", children: [kicker === undefined || !withIdentity ? null : (_jsx("span", { className: "text-micro font-medium text-muted", children: kicker })), _jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1", children: [!withIdentity ? null : (
|
|
49
|
+
/* `display`, not `title`: the record's name is the entry point of the
|
|
50
|
+
screen. At the title size (19 px over a 13 px body, 1.46:1) nothing on
|
|
51
|
+
the sheet stood out and the eye had nowhere to start. */
|
|
52
|
+
_jsx("h1", { className: "min-w-0 truncate text-display font-semibold text-ink", children: title })), tag === undefined ? null : _jsx(Tag, { tone: tag.tone, children: tag.label })] }), subtitle === undefined || !withIdentity ? null : (_jsx("p", { className: "mt-0.5 truncate text-lead text-ink", children: subtitle }))] })] }), figure === undefined || !withIdentity ? null : (_jsxs("div", { className: "ml-auto flex shrink-0 flex-col items-end", children: [_jsx("span", { className: "text-micro font-medium text-muted", children: figure.label }), _jsx("span", { className: "text-display font-semibold tabular-nums text-ink", children: figure.value })] }))] }), withRail || status === undefined ? null : (_jsx("div", { className: "flex min-w-0 flex-wrap items-center gap-1.5", children: status })), aspects === undefined ? null : (
|
|
53
|
+
/* The line is the sheet's mouth. In comfortable it walks to the
|
|
54
|
+
edges; the current tab sits on it and opens into the page below. */
|
|
55
|
+
_jsx("div", { className: `border-b border-line-strong ${byDensity(d, "-mx-4 px-4", "")}`, children: aspects }))] }));
|
|
56
|
+
return (_jsxs(_Fragment, { children: [withRail ? (_jsx(SurfaceProvider, { value: railSurface, children: _jsx("div", { className: "@container/record", children: _jsxs("div", { className: `flex flex-col gap-2 px-3.5 py-2.5 ${railSurface === "chrome" ? "bg-chrome" : "border-b border-line bg-surface"}`, children: [_jsx(Line, { start: crumbs === undefined || crumbs.length === 0 ? null : (_jsx(Breadcrumb, { crumbs: crumbs, surface: railSurface })), center: smart, end: pager }), actions === undefined && status === undefined && dirty === undefined ? null : (
|
|
57
|
+
/* Row 2 — WHAT I CAN DO and WHERE IT IS AT: actions stuck to the
|
|
58
|
+
left edge, the history and the state stuck to the right one. No
|
|
59
|
+
`flex-wrap`: wrapping split the row in two and the state ended up
|
|
60
|
+
below the actions, which is another hierarchy. */
|
|
61
|
+
_jsxs("div", { className: "flex min-w-0 items-start gap-x-4", children: [dirty !== undefined ? (_jsx("div", { className: "flex shrink-0 items-center gap-1.5 @max-handheld/record:hidden", children: _jsx(DirtyActions, { ...dirty }) })) : actions === undefined ? null : (_jsx("div", { className: "flex shrink-0 items-center gap-1.5", children: primaries.length === 0
|
|
62
|
+
? actions
|
|
63
|
+
: railActions.map((node) => isPrimary(node) ? (_jsx("span", { "data-thumb-moves": "", className: "contents @max-handheld/record:hidden", children: node }, node.key)) : (node)) })), status === undefined ? null : (_jsx("div", { className: "ml-auto flex min-w-0 flex-col items-end gap-1.5", children: status }))] }))] }) }) })) : null, _jsx("div", { className: paper ? "bg-ground px-3 pt-3 pb-3" : "", children: _jsx("div", { className: chatter !== undefined && chatterLayout === "aside" ? "@container" : "", children: _jsxs("div", { className: chatter !== undefined && chatterLayout === "aside"
|
|
64
|
+
? "flex min-w-0 flex-col gap-3 @4xl:flex-row @4xl:items-start"
|
|
65
|
+
: "", children: [_jsxs("div", { className: paper
|
|
66
|
+
? `flex w-full max-w-(--container-sheet) min-w-0 flex-1 flex-col overflow-clip rounded-box border border-line bg-surface ${chatter !== undefined && chatterLayout === "aside" ? "" : "mx-auto"}`
|
|
67
|
+
: "flex min-w-0 flex-1 flex-col bg-surface", children: [identity, children, chatter !== undefined && chatterLayout === "page" ? (_jsx("div", { className: "border-t border-line", children: isValidElement(chatter)
|
|
68
|
+
? cloneElement(chatter, {
|
|
69
|
+
embedded: true,
|
|
70
|
+
})
|
|
71
|
+
: chatter })) : null] }), chatter !== undefined && chatterLayout === "aside" ? chatter : null] }) }) }), thumb === null ? null : _jsx(ThumbBar, { mode: thumb, dirty: dirty, primaries: primaries })] }));
|
|
72
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { byDensity, useDensity } from "../../foundations/density.js";
|
|
4
|
+
/* ==================================================================== *
|
|
5
|
+
* LA FICHA, MIENTRAS LLEGA.
|
|
6
|
+
*
|
|
7
|
+
* Factura y cliente contestaban la espera con `Empty`: «Cargando…» en el
|
|
8
|
+
* medio de la página. Eso es la frase de «no hay nada», y el registro sí
|
|
9
|
+
* está — todavía no llegó. La silueta es la de la ficha: el riel, el nombre,
|
|
10
|
+
* los cuatro hechos, las solapas y el cuerpo. Cuando el registro aparece,
|
|
11
|
+
* cae en el mismo lugar.
|
|
12
|
+
*
|
|
13
|
+
* Sin brillo. Un pulso ya quiere decir «este estado está vivo».
|
|
14
|
+
* ==================================================================== */
|
|
15
|
+
const HECHOS = ["w-16", "w-12", "w-20", "w-24"];
|
|
16
|
+
const SOLAPAS = ["w-14", "w-16", "w-20"];
|
|
17
|
+
export function RecordSkeleton({ label }) {
|
|
18
|
+
const d = useDensity();
|
|
19
|
+
const page = d === "comfortable";
|
|
20
|
+
return (_jsxs("article", { "aria-busy": "true", className: "flex min-w-0 flex-col", children: [_jsx("span", { className: "sr-only", children: label }), page ? (_jsxs("div", { "aria-hidden": true, className: "flex items-center gap-3 bg-chrome px-3.5 py-2.5", children: [_jsx("span", { className: "h-3 w-24 rounded-control bg-chrome-ink/15" }), _jsx("span", { className: "h-3 w-40 rounded-control bg-chrome-ink/10" }), _jsx("span", { className: "ml-auto h-(--size-control-sm) w-28 rounded-control bg-chrome-ink/15" })] })) : null, _jsx("div", { "aria-hidden": true, className: page ? "border border-line bg-surface" : "bg-surface", children: _jsxs("div", { className: `flex flex-col ${byDensity(d, "gap-3 px-4 pt-3.5 pb-4", "gap-2 py-2")}`, children: [_jsx("span", { className: "h-5 w-72 max-w-full rounded-control bg-ink/8" }), _jsx("div", { className: "flex flex-wrap gap-x-6 gap-y-2", children: HECHOS.map((width) => (_jsxs("span", { className: "flex flex-col gap-1", children: [_jsx("span", { className: "h-2.5 w-10 rounded-control bg-ink/5" }), _jsx("span", { className: `h-3.5 rounded-control bg-ink/8 ${width}` })] }, width))) }), _jsx("div", { className: "flex gap-4 border-b border-line pb-2", children: SOLAPAS.map((width, i) => (_jsx("span", { className: `h-3 rounded-control ${i === 0 ? "bg-ink/10" : "bg-ink/5"} ${width}` }, width))) }), _jsx("span", { className: "h-24 rounded-box bg-ink/5" })] }) })] }));
|
|
21
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Surface } from "../../foundations/types.js";
|
|
2
|
+
import type { LucideIcon } from "lucide-react";
|
|
3
|
+
import { type ReactNode } from "react";
|
|
4
|
+
/** What hangs off this record, with its number, and can be pressed.
|
|
5
|
+
*
|
|
6
|
+
* It answers TWO questions with a single control: *is there any?* and *how
|
|
7
|
+
* many?*. Without this you have to open it to discover there is nothing — the
|
|
8
|
+
* most expensive click in any interface.
|
|
9
|
+
*
|
|
10
|
+
* **`—` is not `0`.** «I don't know yet» and «there is none» are two different
|
|
11
|
+
* answers, and confusing them makes somebody decide on data that has not
|
|
12
|
+
* arrived yet. */
|
|
13
|
+
export declare function SmartButton({ icon: Icon, count, label, onClick, disabled, surface, layout, }: {
|
|
14
|
+
icon: LucideIcon;
|
|
15
|
+
count?: number;
|
|
16
|
+
label: string;
|
|
17
|
+
onClick?: () => void;
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
/** By default it inherits it from the surface that wraps it. */
|
|
20
|
+
surface?: Surface;
|
|
21
|
+
/**
|
|
22
|
+
* `stacked` — the figure on top and the label below. For the sheet, where
|
|
23
|
+
* there is height.
|
|
24
|
+
* `inline` — everything on one line. For the rail.
|
|
25
|
+
*
|
|
26
|
+
* Context: in the rail, a two-line box next to breadcrumbs and a one-line
|
|
27
|
+
* pager imposes its height on the whole row, and the other two end up
|
|
28
|
+
* floating in the middle of a band that is twice as tall as them. That was
|
|
29
|
+
* «something looks off».
|
|
30
|
+
*/
|
|
31
|
+
layout?: "stacked" | "inline";
|
|
32
|
+
}): import("react").JSX.Element;
|
|
33
|
+
export declare function SmartButtons({ children }: {
|
|
34
|
+
children: ReactNode;
|
|
35
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useUiCopy } from "../../foundations/copy.js";
|
|
4
|
+
import { bySurface, useSurface } from "../../foundations/surface.js";
|
|
5
|
+
import { Children, createContext, useContext } from "react";
|
|
6
|
+
/* ---------------------------------------------------------------- *
|
|
7
|
+
* THE SMART BUTTONS
|
|
8
|
+
* ---------------------------------------------------------------- */
|
|
9
|
+
/** On the chrome the counters share one bar. Each button reads that and drops
|
|
10
|
+
* its own frame, the way a stage drops its own arrow. */
|
|
11
|
+
const SmartGroup = createContext(false);
|
|
12
|
+
/** What hangs off this record, with its number, and can be pressed.
|
|
13
|
+
*
|
|
14
|
+
* It answers TWO questions with a single control: *is there any?* and *how
|
|
15
|
+
* many?*. Without this you have to open it to discover there is nothing — the
|
|
16
|
+
* most expensive click in any interface.
|
|
17
|
+
*
|
|
18
|
+
* **`—` is not `0`.** «I don't know yet» and «there is none» are two different
|
|
19
|
+
* answers, and confusing them makes somebody decide on data that has not
|
|
20
|
+
* arrived yet. */
|
|
21
|
+
export function SmartButton({ icon: Icon, count, label, onClick, disabled = false, surface, layout = "stacked", }) {
|
|
22
|
+
const copy = useUiCopy();
|
|
23
|
+
const empty = count === 0;
|
|
24
|
+
const activeSurface = useSurface(surface);
|
|
25
|
+
const isInline = layout === "inline";
|
|
26
|
+
const grouped = useContext(SmartGroup);
|
|
27
|
+
return (_jsxs("button", { type: "button", onClick: onClick, disabled: disabled || onClick === undefined, className: `flex shrink-0 items-center gap-2 text-left transition-[background-color,border-color,box-shadow] duration-(--duration-instant) disabled:cursor-default disabled:opacity-60 focus-visible:outline-2 focus-visible:outline-primary ${grouped
|
|
28
|
+
? `not-disabled:hover:bg-chrome-ink/10 focus-visible:-outline-offset-2 ${isInline ? "h-(--size-control-sm) px-2.5" : "px-2.5 py-1"}`
|
|
29
|
+
: `rounded-control border focus-visible:outline-offset-2 ${isInline ? "h-(--size-control-sm) px-2" : "px-2.5 py-1"} ${bySurface(activeSurface, "border-ink/12 bg-surface shadow-raised not-disabled:hover:border-primary/40 not-disabled:hover:bg-primary/4", "border-chrome-ink/12 bg-chrome-ink/6 not-disabled:hover:border-chrome-link/40 not-disabled:hover:bg-chrome-ink/12")}`}`, children: [_jsx(Icon, { className: `size-(--size-icon-md) shrink-0 ${bySurface(activeSurface, empty ? "text-muted/50" : "text-muted", empty ? "text-chrome-muted/50" : "text-chrome-muted")}`, strokeWidth: 1.75 }), _jsxs("span", { className: `min-w-0 leading-tight ${isInline ? "flex items-baseline gap-1.5" : ""}`, children: [_jsx("span", { className: `text-meta font-medium tabular-nums ${isInline ? "" : "block"} ${bySurface(activeSurface, empty ? "text-muted" : "text-ink", empty ? "text-chrome-muted" : "text-chrome-ink")}`, children: count === undefined ? "—" : count.toLocaleString(copy.locale) }), _jsx("span", { className: `text-micro ${isInline ? "" : "block"} ${bySurface(activeSurface, "text-muted", "text-chrome-muted")}`, children: label })] })] }));
|
|
30
|
+
}
|
|
31
|
+
export function SmartButtons({ children }) {
|
|
32
|
+
const surface = useSurface();
|
|
33
|
+
if (surface !== "chrome") {
|
|
34
|
+
return _jsx("div", { className: "flex flex-wrap items-stretch gap-1.5", children: children });
|
|
35
|
+
}
|
|
36
|
+
const items = Children.toArray(children);
|
|
37
|
+
return (_jsx(SmartGroup.Provider, { value: true, children: _jsx("div", { className: "flex min-w-0 items-stretch overflow-hidden rounded-control border border-chrome-ink/15 bg-chrome-ink/8", children: items.map((child, i) => (_jsx("div", { className: i === 0 ? "flex min-w-0" : "flex min-w-0 border-l border-chrome-ink/20", children: child }, i))) }) }));
|
|
38
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Surface } from "../../foundations/types.js";
|
|
2
|
+
export interface Stage {
|
|
3
|
+
id: string;
|
|
4
|
+
label: string;
|
|
5
|
+
}
|
|
6
|
+
/** Which step something is on, **with the steps that come next in sight**.
|
|
7
|
+
*
|
|
8
|
+
* ── One track, one language ──────────────────────────────────────────
|
|
9
|
+
*
|
|
10
|
+
* The stages are segments of ONE track, Odoo's statusbar: the order is the
|
|
11
|
+
* direction. What is behind is ink with a check, what comes next is muted, and the
|
|
12
|
+
* current stage is the only FILLED segment — `primary` on the canvas, the chrome's
|
|
13
|
+
* ink inverted on the rail. With `failed` that fill turns `danger` and carries an
|
|
14
|
+
* alert mark, so the failure is not only a color.
|
|
15
|
+
*
|
|
16
|
+
* A clipped arrow per step was tried and dropped: the notch between two arrows
|
|
17
|
+
* reads as a gap — four chips, not a path.
|
|
18
|
+
*
|
|
19
|
+
* ── Narrow: it STACKS, it does not hide ──────────────────────────────
|
|
20
|
+
*
|
|
21
|
+
* Four stages side by side do not fit in a 480 px drawer, and the answer is not
|
|
22
|
+
* tucking them behind a chevron: a state you have to open stops answering «where
|
|
23
|
+
* is this at?», which is the only question it has. It stacks — one stage per row —
|
|
24
|
+
* in the same track, with the same three paints and the same marks. What changes
|
|
25
|
+
* is the DIRECTION, not how much is shown nor how it is painted.
|
|
26
|
+
*
|
|
27
|
+
* ── And it can be CHOSEN ─────────────────────────────────────────────
|
|
28
|
+
*
|
|
29
|
+
* With `onSelect` the track is a React Aria `RadioGroup`: one stage at a time,
|
|
30
|
+
* arrow keys move between them, Tab enters at the chosen one. It is not
|
|
31
|
+
* «advancing» — a stage does not change by pressing it — it is choosing which
|
|
32
|
+
* stage we are talking about: in a list grouped by stage, pressing «generando» asks
|
|
33
|
+
* for those. So the record's CURRENT stage (`current`, the fill, `aria-current`)
|
|
34
|
+
* and the stage being VIEWED (`selected`, the underline, the checked radio) are two
|
|
35
|
+
* different things and are painted differently. Without `onSelect` it is a plain
|
|
36
|
+
* ordered list and the current stage is `aria-current="step"`. */
|
|
37
|
+
export declare function StatusBar({ stages, current, failed, surface: surfaceProp, onSelect, selected, layout, }: {
|
|
38
|
+
stages: readonly Stage[];
|
|
39
|
+
current: string;
|
|
40
|
+
failed?: boolean;
|
|
41
|
+
surface?: Surface;
|
|
42
|
+
/** Present = the stages can be chosen. Nothing advances: it chooses which stage
|
|
43
|
+
* is being talked about. */
|
|
44
|
+
onSelect?: (id: string) => void;
|
|
45
|
+
/** Which one is chosen, when they can be chosen. It is not `current`: one thing
|
|
46
|
+
* is where the record is at, another which stage I am looking at. */
|
|
47
|
+
selected?: string;
|
|
48
|
+
/** `trail` side by side, `stacked` one per row. Without it the surface's density
|
|
49
|
+
* decides — it is what knows how much width there is. */
|
|
50
|
+
layout?: "trail" | "stacked";
|
|
51
|
+
}): import("react").JSX.Element;
|