@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,70 @@
|
|
|
1
|
+
import type { Tone } from "../../foundations/types.js";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
export interface SheetColumn {
|
|
4
|
+
id: string;
|
|
5
|
+
/** The name of the column, just as it came from the file. */
|
|
6
|
+
header: string;
|
|
7
|
+
/** The detected type, underneath the name. It is half the value of a glance:
|
|
8
|
+
* seeing that `importe` was read as text explains the rest of the screen. */
|
|
9
|
+
type?: string;
|
|
10
|
+
numeric?: boolean;
|
|
11
|
+
width?: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function SheetTable({ columns, rows,
|
|
14
|
+
/** The value of a cell, by index. It is a function and not a prebuilt matrix
|
|
15
|
+
* so that the consumer can read from wherever they want without copying a
|
|
16
|
+
* million rows into another structure. */
|
|
17
|
+
value,
|
|
18
|
+
/** Which cells are painted and with what. `undefined` is unpainted, which is
|
|
19
|
+
* the normal case — a spreadsheet with everything colored highlights
|
|
20
|
+
* nothing. */
|
|
21
|
+
tone,
|
|
22
|
+
/** What the cell says on hover: «imputado por la mediana». */
|
|
23
|
+
note, height, rowLabel, band, onBandToggle, caption, onCommit, editable, }: {
|
|
24
|
+
columns: readonly SheetColumn[];
|
|
25
|
+
rows: number;
|
|
26
|
+
value: (row: number, column: SheetColumn, columnIndex: number) => string | number | null;
|
|
27
|
+
tone?: (row: number, column: SheetColumn, columnIndex: number) => Tone | undefined;
|
|
28
|
+
note?: (row: number, column: SheetColumn, columnIndex: number) => string | undefined;
|
|
29
|
+
/** Pixel height of the scroll box. `"auto"` grows with the rows so a
|
|
30
|
+
* twenty-row glance is not a window with its own scrollbar. */
|
|
31
|
+
height?: number | "auto";
|
|
32
|
+
rowLabel?: (row: number) => string;
|
|
33
|
+
/**
|
|
34
|
+
* Esta fila no es una fila: es la cabecera del montón que empieza acá.
|
|
35
|
+
*
|
|
36
|
+
* Agrupar sin perder la virtualización es esto — **una banda es una fila
|
|
37
|
+
* más, de otra clase**. La alternativa era una planilla por montón, y eso
|
|
38
|
+
* son N cuadrículas con N encabezados y N scrolls para un solo conjunto.
|
|
39
|
+
*
|
|
40
|
+
* `depth` es cuán adentro está el montón. La planilla no puede recibir un
|
|
41
|
+
* árbol —está virtualizada por índice, y un índice es una posición en una
|
|
42
|
+
* secuencia—, así que la jerarquía la dice la sangría de cada banda y no el
|
|
43
|
+
* anidamiento del marcado.
|
|
44
|
+
*/
|
|
45
|
+
band?: (row: number) => {
|
|
46
|
+
field?: string;
|
|
47
|
+
label: string;
|
|
48
|
+
count: number;
|
|
49
|
+
open: boolean;
|
|
50
|
+
depth?: number;
|
|
51
|
+
/**
|
|
52
|
+
* Lo que el montón suma EN CADA COLUMNA, en la posición de esa columna.
|
|
53
|
+
*
|
|
54
|
+
* Una banda que pone su total en un rincón obliga a buscar a qué columna
|
|
55
|
+
* corresponde el número. Alineado bajo su columna no hay nada que buscar:
|
|
56
|
+
* la cuadrícula ya dice qué significa, que es para lo que existe una
|
|
57
|
+
* cuadrícula. `null` en una columna que no suma — que es la mayoría.
|
|
58
|
+
*/
|
|
59
|
+
total?: (column: SheetColumn, columnIndex: number) => string | number | null;
|
|
60
|
+
} | undefined;
|
|
61
|
+
onBandToggle?: (row: number) => void;
|
|
62
|
+
caption?: ReactNode;
|
|
63
|
+
/** Present = the spreadsheet is editable. It receives the raw text just as it
|
|
64
|
+
* was typed; converting it to the column's type belongs to the domain, not
|
|
65
|
+
* here. */
|
|
66
|
+
onCommit?: (row: number, column: SheetColumn, columnIndex: number, next: string) => void;
|
|
67
|
+
/** Which cells are editable. Without this, all of them — but a computed column
|
|
68
|
+
* or an identifier is not touched. */
|
|
69
|
+
editable?: (row: number, column: SheetColumn, columnIndex: number) => boolean;
|
|
70
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useUiCopy } from "../../foundations/copy.js";
|
|
4
|
+
import { stickyTop, useScrollBox } from "../../foundations/sticky.js";
|
|
5
|
+
import { Cell } from "../value/Value.js";
|
|
6
|
+
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
7
|
+
import { ChevronRight } from "lucide-react";
|
|
8
|
+
import { useState } from "react";
|
|
9
|
+
const TONE = {
|
|
10
|
+
neutral: "bg-ink/6",
|
|
11
|
+
info: "bg-info/12",
|
|
12
|
+
good: "bg-good/12",
|
|
13
|
+
warn: "bg-warn/15",
|
|
14
|
+
bad: "bg-bad/12",
|
|
15
|
+
accent: "bg-accent/20",
|
|
16
|
+
};
|
|
17
|
+
const WIDTH = 168;
|
|
18
|
+
const HEIGHT = 28;
|
|
19
|
+
export function SheetTable({ columns, rows,
|
|
20
|
+
/** The value of a cell, by index. It is a function and not a prebuilt matrix
|
|
21
|
+
* so that the consumer can read from wherever they want without copying a
|
|
22
|
+
* million rows into another structure. */
|
|
23
|
+
value,
|
|
24
|
+
/** Which cells are painted and with what. `undefined` is unpainted, which is
|
|
25
|
+
* the normal case — a spreadsheet with everything colored highlights
|
|
26
|
+
* nothing. */
|
|
27
|
+
tone,
|
|
28
|
+
/** What the cell says on hover: «imputado por la mediana». */
|
|
29
|
+
note, height = 420, rowLabel, band, onBandToggle, caption, onCommit, editable, }) {
|
|
30
|
+
const copy = useUiCopy();
|
|
31
|
+
const labelOf = rowLabel ?? ((i) => (i + 1).toLocaleString(copy.locale));
|
|
32
|
+
/* `"auto"` es para un conjunto que ya vino CORTADO —una página de ochenta— y
|
|
33
|
+
deja de virtualizar de hecho: sin scroll propio, la ventana visible es la
|
|
34
|
+
caja entera y se dibujan todas las filas. Para los datos crudos —donde son
|
|
35
|
+
un millón— hay que declarar un alto, y ahí sí se dibujan las ~30 que se
|
|
36
|
+
ven. Cuándo la caja es un contenedor de scroll lo decide `useScrollBox`. */
|
|
37
|
+
const caja = useScrollBox(height === "auto" ? {} : { height });
|
|
38
|
+
const v = useVirtualizer({
|
|
39
|
+
count: rows,
|
|
40
|
+
getScrollElement: () => caja.ref.current,
|
|
41
|
+
estimateSize: () => HEIGHT,
|
|
42
|
+
overscan: 12,
|
|
43
|
+
});
|
|
44
|
+
const items = v.getVirtualItems();
|
|
45
|
+
const [active, setActive] = useState(null);
|
|
46
|
+
const [draft, setDraft] = useState(null);
|
|
47
|
+
const editing = draft !== null;
|
|
48
|
+
const isEditable = onCommit !== undefined;
|
|
49
|
+
function canEdit(r, c) {
|
|
50
|
+
if (!isEditable)
|
|
51
|
+
return false;
|
|
52
|
+
const col = columns[c];
|
|
53
|
+
if (col === undefined)
|
|
54
|
+
return false;
|
|
55
|
+
return editable === undefined || editable(r, col, c);
|
|
56
|
+
}
|
|
57
|
+
function goTo(r, c) {
|
|
58
|
+
const rr = Math.max(0, Math.min(rows - 1, r));
|
|
59
|
+
const cc = Math.max(0, Math.min(columns.length - 1, c));
|
|
60
|
+
setActive({ r: rr, c: cc });
|
|
61
|
+
setDraft(null);
|
|
62
|
+
v.scrollToIndex(rr);
|
|
63
|
+
}
|
|
64
|
+
function openCell(r, c, initial) {
|
|
65
|
+
if (!canEdit(r, c))
|
|
66
|
+
return;
|
|
67
|
+
const col = columns[c];
|
|
68
|
+
setActive({ r, c });
|
|
69
|
+
setDraft(initial ?? String(value(r, col, c) ?? ""));
|
|
70
|
+
}
|
|
71
|
+
function commit(dr, dc) {
|
|
72
|
+
if (active === null || draft === null)
|
|
73
|
+
return;
|
|
74
|
+
const col = columns[active.c];
|
|
75
|
+
if (col !== undefined)
|
|
76
|
+
onCommit?.(active.r, col, active.c, draft);
|
|
77
|
+
setDraft(null);
|
|
78
|
+
if (dr !== 0 || dc !== 0)
|
|
79
|
+
goTo(active.r + dr, active.c + dc);
|
|
80
|
+
}
|
|
81
|
+
/* All the keyboard handling in one place. Splitting it between the box and
|
|
82
|
+
each cell is how you end up with spreadsheets whose arrows sometimes move
|
|
83
|
+
and sometimes scroll. */
|
|
84
|
+
function onKey(e) {
|
|
85
|
+
if (active === null)
|
|
86
|
+
return;
|
|
87
|
+
const { r, c } = active;
|
|
88
|
+
if (editing) {
|
|
89
|
+
if (e.key === "Escape") {
|
|
90
|
+
e.preventDefault();
|
|
91
|
+
setDraft(null);
|
|
92
|
+
}
|
|
93
|
+
else if (e.key === "Enter") {
|
|
94
|
+
e.preventDefault();
|
|
95
|
+
commit(e.shiftKey ? -1 : 1, 0);
|
|
96
|
+
}
|
|
97
|
+
else if (e.key === "Tab") {
|
|
98
|
+
e.preventDefault();
|
|
99
|
+
commit(0, e.shiftKey ? -1 : 1);
|
|
100
|
+
}
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
switch (e.key) {
|
|
104
|
+
case "ArrowDown":
|
|
105
|
+
e.preventDefault();
|
|
106
|
+
goTo(r + 1, c);
|
|
107
|
+
return;
|
|
108
|
+
case "ArrowUp":
|
|
109
|
+
e.preventDefault();
|
|
110
|
+
goTo(r - 1, c);
|
|
111
|
+
return;
|
|
112
|
+
case "ArrowRight":
|
|
113
|
+
case "Tab":
|
|
114
|
+
e.preventDefault();
|
|
115
|
+
goTo(r, c + (e.shiftKey && e.key === "Tab" ? -1 : 1));
|
|
116
|
+
return;
|
|
117
|
+
case "ArrowLeft":
|
|
118
|
+
e.preventDefault();
|
|
119
|
+
goTo(r, c - 1);
|
|
120
|
+
return;
|
|
121
|
+
case "Enter":
|
|
122
|
+
case "F2":
|
|
123
|
+
e.preventDefault();
|
|
124
|
+
openCell(r, c);
|
|
125
|
+
return;
|
|
126
|
+
case "Delete":
|
|
127
|
+
case "Backspace":
|
|
128
|
+
e.preventDefault();
|
|
129
|
+
if (canEdit(r, c)) {
|
|
130
|
+
const col = columns[c];
|
|
131
|
+
if (col !== undefined)
|
|
132
|
+
onCommit?.(r, col, c, "");
|
|
133
|
+
}
|
|
134
|
+
return;
|
|
135
|
+
default:
|
|
136
|
+
/* Typing with the cell active starts editing and keeps the keystroke:
|
|
137
|
+
it is what anyone coming from a spreadsheet does, and losing the first
|
|
138
|
+
character feels like the app is not responding. */
|
|
139
|
+
if (e.key.length === 1 && !e.metaKey && !e.ctrlKey && !e.altKey) {
|
|
140
|
+
e.preventDefault();
|
|
141
|
+
openCell(r, c, e.key);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return (_jsxs("div", { className: "flex min-w-0 flex-col", children: [caption === undefined ? null : (_jsx("div", { className: "px-3 pb-2 text-meta text-muted", children: caption })), _jsx("div", { ref: caja.ref, style: caja.style, onKeyDown: isEditable ? onKey : undefined, tabIndex: isEditable ? 0 : undefined, className: `relative min-w-0 rounded-box border border-line bg-surface focus:outline-none ${caja.className}`, role: "grid", "aria-rowcount": rows, "aria-colcount": columns.length + 1, children: _jsxs("div", { style: {
|
|
146
|
+
minWidth: `${ROW_LABEL_WIDTH + columns.reduce((a, c) => a + (c.width ?? WIDTH), 0)}px`,
|
|
147
|
+
}, children: [_jsxs("div", { className: "sticky z-20 flex border-b border-line-strong bg-ground", style: stickyTop, role: "row", children: [_jsx("div", { className: "sticky left-0 z-30 shrink-0 border-r border-line bg-ground", style: { width: ROW_LABEL_WIDTH } }), columns.map((c) => (_jsxs("div", { role: "columnheader", style: { width: c.width ?? WIDTH }, className: `flex shrink-0 flex-col justify-center gap-0.5 border-r border-line px-2.5 py-1.5 ${c.numeric === true ? "items-end" : "items-start"}`, children: [_jsx("span", { className: "max-w-full truncate text-meta font-medium text-ink", title: c.header, children: c.header }), c.type === undefined ? null : (_jsx("span", { className: "max-w-full truncate font-mono text-micro text-muted", children: c.type }))] }, c.id))), _jsx("div", { className: "flex-1" })] }), _jsx("div", { style: { height: v.getTotalSize(), position: "relative" }, children: items.map((item) => {
|
|
148
|
+
const montón = band?.(item.index);
|
|
149
|
+
if (montón !== undefined) {
|
|
150
|
+
/* La banda queda pegada a la IZQUIERDA como el número de fila:
|
|
151
|
+
con cuarenta columnas, un rótulo que se va con el scroll
|
|
152
|
+
horizontal deja de decir en qué montón estás justo cuando
|
|
153
|
+
más lejos estás de él. */
|
|
154
|
+
return (
|
|
155
|
+
/* La banda RESPETA la cuadrícula: rótulo en la columna fija de
|
|
156
|
+
la izquierda, y después una celda por columna —vacía, salvo
|
|
157
|
+
que esa columna sume algo—. Antes era un rótulo suelto que
|
|
158
|
+
ignoraba las columnas, y entonces un total quedaba en un
|
|
159
|
+
rincón sin decir de qué columna era. */
|
|
160
|
+
_jsxs("div", { role: "row", "aria-rowindex": item.index + 1, className: "absolute top-0 left-0 flex w-full border-b border-line-strong bg-ground", style: { height: item.size, transform: `translateY(${item.start}px)` }, children: [_jsx("button", { type: "button", onClick: onBandToggle === undefined
|
|
161
|
+
? undefined
|
|
162
|
+
: () => onBandToggle(item.index), "aria-expanded": montón.open, className: "sticky left-0 z-10 flex min-w-0 shrink-0 items-center gap-1.5 border-r border-line bg-ground px-2.5 text-left", style: {
|
|
163
|
+
height: "100%",
|
|
164
|
+
/* El mismo ancho que la columna del número de fila, para
|
|
165
|
+
que el rótulo no corra la cuadrícula. */
|
|
166
|
+
width: ROW_LABEL_WIDTH,
|
|
167
|
+
...(montón.depth === undefined || montón.depth === 0
|
|
168
|
+
? {}
|
|
169
|
+
: {
|
|
170
|
+
paddingLeft: `calc(var(--spacing-indent) * ${montón.depth} + 0.625rem)`,
|
|
171
|
+
}),
|
|
172
|
+
}, children: onBandToggle === undefined ? null : (_jsx(ChevronRight, { className: `size-(--size-icon-sm) shrink-0 text-muted transition-transform duration-(--duration-instant) ${montón.open ? "rotate-90" : ""}`, strokeWidth: 2, "aria-hidden": true })) }), columns.map((c, ci) => {
|
|
173
|
+
const total = montón.total?.(c, ci) ?? null;
|
|
174
|
+
return (_jsx("div", { role: "gridcell", style: { width: c.width ?? WIDTH }, className: `flex shrink-0 items-center overflow-hidden border-r border-line px-2.5 whitespace-nowrap ${c.numeric === true ? "justify-end" : "justify-start"}`, children: ci === 0 ? (_jsxs("span", { className: "flex min-w-0 items-center gap-1.5", children: [montón.field === undefined ? null : (_jsx("span", { className: "shrink-0 text-micro text-muted/70", children: montón.field })), _jsx("span", { className: "truncate text-micro font-medium text-ink", children: montón.label }), _jsx("span", { className: "inline-flex shrink-0 items-center rounded-pill border border-line-strong bg-ink/5 px-2 py-0.5 text-micro tabular-nums text-muted", children: montón.count.toLocaleString(copy.locale) })] })) : total === null ? null : (_jsx("span", { className: "truncate text-meta font-medium tabular-nums text-ink", children: typeof total === "number"
|
|
175
|
+
? total.toLocaleString(copy.locale)
|
|
176
|
+
: total })) }, c.id));
|
|
177
|
+
})] }, item.key));
|
|
178
|
+
}
|
|
179
|
+
return (_jsxs("div", { role: "row", "aria-rowindex": item.index + 1, className: "absolute top-0 left-0 flex w-full border-b border-line", style: { height: item.size, transform: `translateY(${item.start}px)` }, children: [_jsx("div", { className: "sticky left-0 z-10 flex shrink-0 items-center justify-end border-r border-line bg-surface px-2 text-micro tabular-nums text-muted", style: { width: ROW_LABEL_WIDTH }, children: labelOf(item.index) }), columns.map((c, ci) => {
|
|
180
|
+
const t = tone?.(item.index, c, ci);
|
|
181
|
+
const n = note?.(item.index, c, ci);
|
|
182
|
+
const isActive = active !== null && active.r === item.index && active.c === ci;
|
|
183
|
+
const isEditingHere = isActive && editing;
|
|
184
|
+
return (_jsx("div", { role: "gridcell", title: n, "aria-selected": isActive, onClick: isEditable ? () => goTo(item.index, ci) : undefined, onDoubleClick: isEditable ? () => openCell(item.index, ci) : undefined, style: { width: c.width ?? WIDTH }, className: `relative flex shrink-0 items-center overflow-hidden border-r border-line px-2.5 text-meta tabular-nums whitespace-nowrap text-ink ${c.numeric === true ? "justify-end" : "justify-start"} ${t === undefined ? "" : TONE[t]} ${
|
|
185
|
+
/* The outline of the active cell goes on the inside
|
|
186
|
+
(`inset-ring`) and not on the outside: a 2 px border
|
|
187
|
+
would push the content and the whole row would dance
|
|
188
|
+
as you move around with the arrows. */
|
|
189
|
+
isActive
|
|
190
|
+
? "inset-ring-2 inset-ring-primary"
|
|
191
|
+
: isEditable
|
|
192
|
+
? "cursor-cell"
|
|
193
|
+
: ""}`, children: isEditingHere ? (_jsx("input", { autoFocus: true, value: draft, onChange: (ev) => setDraft(ev.target.value), onBlur: () => commit(0, 0), className: `absolute inset-0 w-full bg-surface px-2.5 text-meta tabular-nums text-ink outline-none ${c.numeric === true ? "text-right" : "text-left"}` })) : (_jsx(Cell, { value: value(item.index, c, ci), max: 24 })) }, c.id));
|
|
194
|
+
}), _jsx("div", { className: "flex-1" })] }, item.key));
|
|
195
|
+
}) })] }) })] }));
|
|
196
|
+
}
|
|
197
|
+
const ROW_LABEL_WIDTH = 64;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { Tone } from "../../foundations/types.js";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
export type SortDirection = "asc" | "desc";
|
|
4
|
+
export interface SortState {
|
|
5
|
+
key: string;
|
|
6
|
+
direction: SortDirection;
|
|
7
|
+
}
|
|
8
|
+
export declare function Table({ children,
|
|
9
|
+
/** With a height, the table scrolls on its own and the header sticks.
|
|
10
|
+
* Without a height, the page scrolls and the header goes away. */
|
|
11
|
+
height, }: {
|
|
12
|
+
children: ReactNode;
|
|
13
|
+
/** A number means pixels; a string goes through as is, so `26rem` follows the
|
|
14
|
+
* system scale instead of pinning a size in pixels. */
|
|
15
|
+
height?: number | string;
|
|
16
|
+
}): import("react").JSX.Element;
|
|
17
|
+
export declare function THead({ children }: {
|
|
18
|
+
children: ReactNode;
|
|
19
|
+
}): import("react").JSX.Element;
|
|
20
|
+
/** A column. If it gets a `sortKey`, it can be sorted.
|
|
21
|
+
*
|
|
22
|
+
* The icon is ALWAYS there, dimmed when the column is not the sorted one: an
|
|
23
|
+
* indicator that only shows up once you sort does not say which columns are
|
|
24
|
+
* sortable, and then you have to try them one by one. */
|
|
25
|
+
export declare function Th({ children, sortKey, sort, onSort, numeric, align, width, }: {
|
|
26
|
+
children: ReactNode;
|
|
27
|
+
sortKey?: string;
|
|
28
|
+
sort?: SortState | null;
|
|
29
|
+
onSort?: (next: SortState | null) => void;
|
|
30
|
+
/** Numbers align to the right: that is what lets you compare orders of
|
|
31
|
+
* magnitude without reading the figures. */
|
|
32
|
+
numeric?: boolean;
|
|
33
|
+
/** The alignment, when it is NOT because it is a number. They are two
|
|
34
|
+
* different things: `numeric` says «this is a figure» and drags along the
|
|
35
|
+
* right alignment and the tabular figures; `align` only pushes. A status
|
|
36
|
+
* label on the right is not a number. */
|
|
37
|
+
align?: "left" | "right";
|
|
38
|
+
/** A number means pixels; a string goes through as is, so `6rem` follows the
|
|
39
|
+
* system scale instead of pinning a size in pixels. */
|
|
40
|
+
width?: number | string;
|
|
41
|
+
}): import("react").JSX.Element;
|
|
42
|
+
export declare function Tr({ children, onClick, selected, }: {
|
|
43
|
+
children: ReactNode;
|
|
44
|
+
onClick?: () => void;
|
|
45
|
+
selected?: boolean;
|
|
46
|
+
}): import("react").JSX.Element;
|
|
47
|
+
/**
|
|
48
|
+
* A row that opens its own detail underneath.
|
|
49
|
+
*
|
|
50
|
+
* Context: `DataTable` expands GROUPS —TanStack takes care of that— and that is
|
|
51
|
+
* not enough for the most common case in this product: a column that opens up
|
|
52
|
+
* to show its distribution. That is not a group, it is the detail of ONE row,
|
|
53
|
+
* and every view was writing it by hand with its own `colSpan` and its own
|
|
54
|
+
* chevron.
|
|
55
|
+
*
|
|
56
|
+
* The trigger is the first cell and not a column of chevrons: a whole column
|
|
57
|
+
* for a 12 px control is width that a twelve-column table needs.
|
|
58
|
+
*/
|
|
59
|
+
export declare function TrDetail({ children, detail, open, onToggle, columns, label, selected, }: {
|
|
60
|
+
children: ReactNode;
|
|
61
|
+
detail: ReactNode;
|
|
62
|
+
open: boolean;
|
|
63
|
+
onToggle: () => void;
|
|
64
|
+
/** How many columns the table has, so the detail spans all of them. */
|
|
65
|
+
columns: number;
|
|
66
|
+
/** What is being opened, for the screen reader: «distribución de importe». */
|
|
67
|
+
label: string;
|
|
68
|
+
selected?: boolean;
|
|
69
|
+
}): import("react").JSX.Element;
|
|
70
|
+
/** The background of a cell, by tone. It replaces the row wash on that cell:
|
|
71
|
+
* a hovered row and a painted cell are two signals, and stacking them reads
|
|
72
|
+
* as a third color. */
|
|
73
|
+
export declare const CELL_TONE: Record<Tone, string>;
|
|
74
|
+
export declare function Td({ children, numeric, align,
|
|
75
|
+
/** A CODE — an id, a hash, a key — set in the monospaced face. Off by default: a
|
|
76
|
+
* number does not need it (a numeric cell gets tabular figures on its own), and a
|
|
77
|
+
* name in mono reads like a log, not like a report. */
|
|
78
|
+
mono,
|
|
79
|
+
/** Paints the whole cell. For what the eye has to find without reading: an
|
|
80
|
+
* imputed value, one out of range, a row that changed.
|
|
81
|
+
*
|
|
82
|
+
* Context: it is the CELL and not the text. Tinting the text says «this
|
|
83
|
+
* number is of another kind»; tinting the cell says «look here», which is
|
|
84
|
+
* something else — and in a table of forty rows by six columns, colored text
|
|
85
|
+
* gets lost. */
|
|
86
|
+
tone, title, }: {
|
|
87
|
+
children: ReactNode;
|
|
88
|
+
numeric?: boolean;
|
|
89
|
+
/** Pushes without saying it is a number. See the note on `Th`. */
|
|
90
|
+
align?: "left" | "right";
|
|
91
|
+
mono?: boolean;
|
|
92
|
+
tone?: Tone;
|
|
93
|
+
/** Why it is painted. A colored cell with no explanation is a question. */
|
|
94
|
+
title?: string;
|
|
95
|
+
}): import("react").JSX.Element;
|
|
96
|
+
/** The selection checkbox, with its third state.
|
|
97
|
+
*
|
|
98
|
+
* «some» is not an ornament: without it, the header checkbox of a table with
|
|
99
|
+
* three of forty rows selected lies in both directions — empty says «none»
|
|
100
|
+
* and filled says «all». */
|
|
101
|
+
export declare function SelectBox({ checked, indeterminate, onChange, label, }: {
|
|
102
|
+
checked: boolean;
|
|
103
|
+
indeterminate?: boolean;
|
|
104
|
+
onChange?: (next: boolean) => void;
|
|
105
|
+
label: string;
|
|
106
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { stickyTop, useScrollBox } from "../../foundations/sticky.js";
|
|
4
|
+
import { ArrowDown, ArrowUp, ChevronRight, ChevronsUpDown } from "lucide-react";
|
|
5
|
+
import { createContext, useContext, useId } from "react";
|
|
6
|
+
export function Table({ children,
|
|
7
|
+
/** With a height, the table scrolls on its own and the header sticks.
|
|
8
|
+
* Without a height, the page scrolls and the header goes away. */
|
|
9
|
+
height, }) {
|
|
10
|
+
const caja = useScrollBox(height === undefined ? {} : { height });
|
|
11
|
+
return (
|
|
12
|
+
/* Cuándo esta caja es un contenedor de scroll y cuándo no lo decide
|
|
13
|
+
`useScrollBox`: ahí vive el dilema entero y ahí vive su explicación. Las
|
|
14
|
+
tres tablas del sistema lo resolvían con tres copias de la misma
|
|
15
|
+
medición. */
|
|
16
|
+
_jsx("div", { ref: caja.ref, className: `min-w-0 ${caja.className}`, style: caja.style, children: _jsx("table", { className: "w-full border-separate border-spacing-0 px-1 text-left", children: children }) }));
|
|
17
|
+
}
|
|
18
|
+
export function THead({ children }) {
|
|
19
|
+
return (
|
|
20
|
+
/* Se pega DEBAJO de lo que ya esté pegado —la barra de la aplicación y la
|
|
21
|
+
de la lista— y no en `top: 0`, que lo metía atrás de las dos. Sin alto en
|
|
22
|
+
`Table` el que scrollea es la página, y ahí la diferencia es justamente
|
|
23
|
+
la que hacía desaparecer los nombres de columna. Ver `system/sticky.tsx`. */
|
|
24
|
+
_jsx("thead", { className: "sticky z-10 bg-ground", style: stickyTop, children: _jsx("tr", { children: children }) }));
|
|
25
|
+
}
|
|
26
|
+
/** A column. If it gets a `sortKey`, it can be sorted.
|
|
27
|
+
*
|
|
28
|
+
* The icon is ALWAYS there, dimmed when the column is not the sorted one: an
|
|
29
|
+
* indicator that only shows up once you sort does not say which columns are
|
|
30
|
+
* sortable, and then you have to try them one by one. */
|
|
31
|
+
export function Th({ children, sortKey, sort, onSort, numeric = false, align, width, }) {
|
|
32
|
+
const active = sortKey !== undefined && sort?.key === sortKey;
|
|
33
|
+
const dir = active ? sort.direction : undefined;
|
|
34
|
+
function nextSort() {
|
|
35
|
+
if (sortKey === undefined || onSort === undefined)
|
|
36
|
+
return;
|
|
37
|
+
// asc → desc → unsorted. The third click puts the table back the way it was.
|
|
38
|
+
if (!active)
|
|
39
|
+
onSort({ key: sortKey, direction: "asc" });
|
|
40
|
+
else if (dir === "asc")
|
|
41
|
+
onSort({ key: sortKey, direction: "desc" });
|
|
42
|
+
else
|
|
43
|
+
onSort(null);
|
|
44
|
+
}
|
|
45
|
+
const Icon = dir === "asc" ? ArrowUp : dir === "desc" ? ArrowDown : ChevronsUpDown;
|
|
46
|
+
return (_jsx("th", { scope: "col", style: width === undefined ? undefined : { width }, "aria-sort": active ? (dir === "asc" ? "ascending" : "descending") : "none", className: `border-b border-line-strong px-3 py-2 text-meta font-medium whitespace-nowrap text-muted ${(align ?? (numeric ? "right" : "left")) === "right" ? "text-right" : "text-left"}`, children: sortKey === undefined ? (children) : (_jsxs("button", { type: "button", onClick: nextSort, className: `inline-flex items-center gap-1 rounded-control transition-colors duration-(--duration-instant) hover:text-ink focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-primary ${(align ?? (numeric ? "right" : "left")) === "right" ? "flex-row-reverse" : ""} ${active ? "text-ink" : ""}`, children: [children, _jsx(Icon, { className: `size-(--size-icon-sm) shrink-0 ${active ? "text-primary" : "text-line-strong"}`, strokeWidth: 2, "aria-hidden": true })] })) }));
|
|
47
|
+
}
|
|
48
|
+
/** The wash of a row lives on the cells, not on the `<tr>`. A background on the
|
|
49
|
+
* row paints edge to edge; on the cells the ends can round and the table's
|
|
50
|
+
* padding keeps the mark off the card. */
|
|
51
|
+
const RowMark = createContext(false);
|
|
52
|
+
const ROW_WASH = "group-hover/row:bg-ink/4";
|
|
53
|
+
const ROW_ON = "bg-primary/8";
|
|
54
|
+
function rowWash(selected) {
|
|
55
|
+
return selected ? ROW_ON : ROW_WASH;
|
|
56
|
+
}
|
|
57
|
+
export function Tr({ children, onClick, selected = false, }) {
|
|
58
|
+
return (_jsx(RowMark.Provider, { value: selected, children: _jsx("tr", { onClick: onClick, className: `group/row ${onClick === undefined ? "" : "cursor-pointer"}`, children: children }) }));
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* A row that opens its own detail underneath.
|
|
62
|
+
*
|
|
63
|
+
* Context: `DataTable` expands GROUPS —TanStack takes care of that— and that is
|
|
64
|
+
* not enough for the most common case in this product: a column that opens up
|
|
65
|
+
* to show its distribution. That is not a group, it is the detail of ONE row,
|
|
66
|
+
* and every view was writing it by hand with its own `colSpan` and its own
|
|
67
|
+
* chevron.
|
|
68
|
+
*
|
|
69
|
+
* The trigger is the first cell and not a column of chevrons: a whole column
|
|
70
|
+
* for a 12 px control is width that a twelve-column table needs.
|
|
71
|
+
*/
|
|
72
|
+
export function TrDetail({ children, detail, open, onToggle, columns, label, selected = false, }) {
|
|
73
|
+
const id = useId();
|
|
74
|
+
return (_jsx(RowMark.Provider, { value: selected, children: _jsxs(_Fragment, { children: [_jsxs("tr", { onClick: onToggle, className: "group/row cursor-pointer", children: [_jsx("td", { className: `border-b border-line py-1.5 pr-0 pl-2.5 align-middle first:rounded-l-control ${rowWash(selected)}`, children: _jsx("button", { type: "button", onClick: (e) => {
|
|
75
|
+
e.stopPropagation();
|
|
76
|
+
onToggle();
|
|
77
|
+
}, "aria-expanded": open, "aria-controls": id, "aria-label": label, className: "inline-flex size-5 items-center justify-center rounded-pill text-muted transition-colors duration-(--duration-instant) hover:bg-ink/6 hover:text-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary", children: _jsx(ChevronRight, { className: `size-(--size-icon-sm) transition-transform duration-(--duration-instant) ${open ? "rotate-90" : ""}`, strokeWidth: 2 }) }) }), children] }), _jsx("tr", { id: id, hidden: !open, children: _jsx("td", { colSpan: columns + 1, className: "border-b border-line bg-ground px-3 py-3", children: detail }) })] }) }));
|
|
78
|
+
}
|
|
79
|
+
/** The background of a cell, by tone. It replaces the row wash on that cell:
|
|
80
|
+
* a hovered row and a painted cell are two signals, and stacking them reads
|
|
81
|
+
* as a third color. */
|
|
82
|
+
export const CELL_TONE = {
|
|
83
|
+
neutral: "bg-ink/6",
|
|
84
|
+
info: "bg-info/12",
|
|
85
|
+
good: "bg-good/12",
|
|
86
|
+
warn: "bg-warn/15",
|
|
87
|
+
bad: "bg-bad/12",
|
|
88
|
+
accent: "bg-accent/20",
|
|
89
|
+
};
|
|
90
|
+
export function Td({ children, numeric = false, align,
|
|
91
|
+
/** A CODE — an id, a hash, a key — set in the monospaced face. Off by default: a
|
|
92
|
+
* number does not need it (a numeric cell gets tabular figures on its own), and a
|
|
93
|
+
* name in mono reads like a log, not like a report. */
|
|
94
|
+
mono = false,
|
|
95
|
+
/** Paints the whole cell. For what the eye has to find without reading: an
|
|
96
|
+
* imputed value, one out of range, a row that changed.
|
|
97
|
+
*
|
|
98
|
+
* Context: it is the CELL and not the text. Tinting the text says «this
|
|
99
|
+
* number is of another kind»; tinting the cell says «look here», which is
|
|
100
|
+
* something else — and in a table of forty rows by six columns, colored text
|
|
101
|
+
* gets lost. */
|
|
102
|
+
tone, title, }) {
|
|
103
|
+
const selected = useContext(RowMark);
|
|
104
|
+
return (_jsx("td", { title: title, className: `border-b border-line px-3 py-1.5 text-meta text-ink first:rounded-l-control last:rounded-r-control ${(align ?? (numeric ? "right" : "left")) === "right" ? "text-right" : "text-left"} ${mono ? "font-mono tabular-nums" : numeric ? "tabular-nums" : ""} ${tone === undefined ? rowWash(selected) : CELL_TONE[tone]}`, children: children }));
|
|
105
|
+
}
|
|
106
|
+
/** The selection checkbox, with its third state.
|
|
107
|
+
*
|
|
108
|
+
* «some» is not an ornament: without it, the header checkbox of a table with
|
|
109
|
+
* three of forty rows selected lies in both directions — empty says «none»
|
|
110
|
+
* and filled says «all». */
|
|
111
|
+
export function SelectBox({ checked, indeterminate = false, onChange, label, }) {
|
|
112
|
+
return (_jsx("input", { type: "checkbox", checked: checked, "aria-label": label, ref: (el) => {
|
|
113
|
+
if (el !== null)
|
|
114
|
+
el.indeterminate = indeterminate && !checked;
|
|
115
|
+
}, onChange: (e) => onChange?.(e.target.checked), onClick: (e) => e.stopPropagation(), className: "size-3.5 shrink-0 cursor-pointer accent-primary" }));
|
|
116
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { type Aggregate, type DataColumn, DataTable, type GroupColumn, } from "./DataTable.js";
|
|
2
|
+
export { EditableTable, type EditColumn, type EditLayout, type StackPlace, stackPlaces, } from "./EditableTable.js";
|
|
3
|
+
export { PivotTable, type PivotTableProps, } from "./PivotTable.js";
|
|
4
|
+
export { type ReportColumn, type ReportRow, type ReportRowKind, ReportTable, } from "./ReportTable.js";
|
|
5
|
+
export { type RowAction, RowActions } from "./RowActions.js";
|
|
6
|
+
export { type SheetColumn, SheetTable } from "./SheetTable.js";
|
|
7
|
+
export { CELL_TONE, type SortDirection, type SortState, Table, Td, Th, THead, Tr, } from "./Table.js";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/* The presentation (`Table`) is ours and is used on its own; `DataTable` is the
|
|
2
|
+
wiring to TanStack for grouping and sorting. See the note in `DataTable.tsx`. */
|
|
3
|
+
export { DataTable, } from "./DataTable.js";
|
|
4
|
+
export { EditableTable, stackPlaces, } from "./EditableTable.js";
|
|
5
|
+
export { PivotTable, } from "./PivotTable.js";
|
|
6
|
+
export { ReportTable, } from "./ReportTable.js";
|
|
7
|
+
export { RowActions } from "./RowActions.js";
|
|
8
|
+
export { SheetTable } from "./SheetTable.js";
|
|
9
|
+
export { CELL_TONE, Table, Td, Th, THead, Tr, } from "./Table.js";
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { Tone } from "../../foundations/types.js";
|
|
2
|
+
import type { LucideIcon } from "lucide-react";
|
|
3
|
+
/**
|
|
4
|
+
* UNA TIRA DE PESTAÑAS. La misma en todo el sistema.
|
|
5
|
+
*
|
|
6
|
+
* Context: esto vivía adentro del encabezado del registro y se llamaba
|
|
7
|
+
* `Aspects`, así que el chatter —que necesita exactamente lo mismo— no podía
|
|
8
|
+
* usarlo: un contexto no importa de otro contexto. El resultado era una tira de
|
|
9
|
+
* pestañas de segunda mano, con el activo marcado de otra manera, en la misma
|
|
10
|
+
* pantalla que la buena.
|
|
11
|
+
*
|
|
12
|
+
* Es una primitiva porque no sabe qué está separando: las caras de un registro,
|
|
13
|
+
* las dos fuentes de una historia, o lo que venga.
|
|
14
|
+
*
|
|
15
|
+
* ── Por qué el activo es una PESTAÑA de carpeta ─────────────────────
|
|
16
|
+
*
|
|
17
|
+
* Y no un subrayado. Un subrayado dice «esto está elegido»; una pestaña que sale
|
|
18
|
+
* del borde y tapa la línea dice «esto está ABIERTO y lo de abajo es su
|
|
19
|
+
* contenido», que es la relación real. La diferencia importa cuando hay tres y
|
|
20
|
+
* el contenido de abajo no cambia de forma al cambiar de una a otra.
|
|
21
|
+
*
|
|
22
|
+
* ── El contador apagado en cero ─────────────────────────────────────
|
|
23
|
+
*
|
|
24
|
+
* Un cero se dibuja, y apagado. Esconderlo obliga a abrir la pestaña para
|
|
25
|
+
* descubrir que está vacía; pintarlo como los demás la hace parecer que tiene
|
|
26
|
+
* algo. «No hay ninguna» es información y se dice.
|
|
27
|
+
*
|
|
28
|
+
* ── El contador y la NOTIFICACIÓN son dos cosas ─────────────────────
|
|
29
|
+
*
|
|
30
|
+
* El contador dice CUÁNTAS cosas hay detrás, y es neutro: «pagos 0» es una
|
|
31
|
+
* respuesta tan válida como «pagos 5». La notificación dice que algo de ahí
|
|
32
|
+
* PIDE que lo mires —falló, cambió, llegó— y por eso tiene color y por eso un
|
|
33
|
+
* cero no se dibuja: no hay nada que pedir.
|
|
34
|
+
*
|
|
35
|
+
* Confundirlos se ve así: una pestaña con «3» al lado y nadie sabe si son tres
|
|
36
|
+
* cosas o tres problemas. Son dos marcas porque son dos preguntas.
|
|
37
|
+
*/
|
|
38
|
+
/**
|
|
39
|
+
* ALGO DE ESTA PESTAÑA PIDE ATENCIÓN.
|
|
40
|
+
*
|
|
41
|
+
* No es un conteo: un conteo dice cuántas cosas hay y esto dice que hay algo que
|
|
42
|
+
* mirar. Por eso tiene color, por eso no se dibuja en cero, y por eso puede
|
|
43
|
+
* convivir con el contador — «pagos 4 ⬤» es cuatro cosas, una de ellas
|
|
44
|
+
* rechazada.
|
|
45
|
+
*/
|
|
46
|
+
export type TabNotice = {
|
|
47
|
+
/**
|
|
48
|
+
* Qué es lo que pide atención, en la voz del producto: «3 sin leer», «la
|
|
49
|
+
* factura no se pudo enviar».
|
|
50
|
+
*
|
|
51
|
+
* Obligatorio, y por la misma razón que en `StatusDot`: un punto de color sin
|
|
52
|
+
* su palabra al lado obliga a aprender una leyenda que no está escrita en
|
|
53
|
+
* ningún lado, y no le dice nada a quien no distingue el rojo del verde. Acá
|
|
54
|
+
* la marca acelera el barrido y la palabra informa — se lee al pasar por
|
|
55
|
+
* encima y la lee un lector de pantalla.
|
|
56
|
+
*/
|
|
57
|
+
label: string;
|
|
58
|
+
/** Cuántas cosas lo piden. Sin esto es un punto, que es lo que se pone cuando
|
|
59
|
+
* llegó algo nuevo y contarlo no agrega nada. Un `0` no dibuja nada: no hay
|
|
60
|
+
* nada que pedir. */
|
|
61
|
+
count?: number;
|
|
62
|
+
/** De qué clase: `bad` algo falló, `warn` algo avisa, `accent` o `info` algo
|
|
63
|
+
* llegó. Sin esto, `bad` — que es el caso por el que esto existe. */
|
|
64
|
+
tone?: Tone;
|
|
65
|
+
};
|
|
66
|
+
export type TabItem = {
|
|
67
|
+
id: string;
|
|
68
|
+
label: string;
|
|
69
|
+
/** La marca de la pestaña. Con tres o más, el ícono es lo que se reconoce sin
|
|
70
|
+
* leer — y en una tira angosta es lo único que sobrevive al truncado. */
|
|
71
|
+
icon?: LucideIcon;
|
|
72
|
+
/** Cuántas cosas hay detrás. `undefined` = no se cuenta; `0` se dibuja
|
|
73
|
+
* apagado, que es distinto de no contar. */
|
|
74
|
+
count?: number;
|
|
75
|
+
/** Que algo de acá pide que lo mires. Aparte del contador: ver `TabNotice`. */
|
|
76
|
+
notice?: TabNotice;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* The strip only: what each tab opens is drawn by whoever uses it (`Record`, `Chatter`).
|
|
80
|
+
*
|
|
81
|
+
* Built on React Aria's `Tabs`, so the strip is a real tab list for the keyboard: the
|
|
82
|
+
* arrows move between tabs and select them, Home/End jump to the ends, and only the open
|
|
83
|
+
* tab is in the Tab order. The hand-written version had the roles and none of the keys.
|
|
84
|
+
*/
|
|
85
|
+
export declare function Tabs({ tabs, current, onSelect, label, }: {
|
|
86
|
+
tabs: readonly TabItem[];
|
|
87
|
+
current: string;
|
|
88
|
+
onSelect?: (id: string) => void;
|
|
89
|
+
/** El rótulo accesible de la tira. Sin esto, «las caras del registro». */
|
|
90
|
+
label?: string;
|
|
91
|
+
}): import("react").JSX.Element;
|