@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,57 @@
|
|
|
1
|
+
import { type FormMode } from "./mode.js";
|
|
2
|
+
export interface CheckItem {
|
|
3
|
+
value: string;
|
|
4
|
+
label: string;
|
|
5
|
+
/** The second line: what the thing is, not a second name. */
|
|
6
|
+
detail?: string;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* One yes/no answer of a form: «Exento de IVA», «Enviar la factura por correo al
|
|
11
|
+
* publicar».
|
|
12
|
+
*
|
|
13
|
+
* Context: it is React Aria's `Checkbox` — a native checkbox input inside the label, so the
|
|
14
|
+
* whole sentence is the click target, Space toggles it and the form reads it as a checkbox.
|
|
15
|
+
* `mixed` is its indeterminate state: the parent of a group where only some are on, and
|
|
16
|
+
* pressing it turns them ALL on — never all off, because «some» is closer to «all» than to
|
|
17
|
+
* «none» in what the person was doing.
|
|
18
|
+
*
|
|
19
|
+
* Inside a `Field` it is named by the field's label AND its own sentence, and described by
|
|
20
|
+
* the field's hint and error — the same wiring as `Switch`.
|
|
21
|
+
*/
|
|
22
|
+
export declare function Checkbox({ checked, mixed, onChange, label, disabled, mode, id: ownId, describedBy: ownDescribedBy, invalid: ownInvalid, }: {
|
|
23
|
+
checked: boolean;
|
|
24
|
+
/** Some of the children are on. Pressing it turns them all on. */
|
|
25
|
+
mixed?: boolean;
|
|
26
|
+
onChange?: (next: boolean) => void;
|
|
27
|
+
label: string;
|
|
28
|
+
disabled?: boolean;
|
|
29
|
+
mode?: FormMode;
|
|
30
|
+
id?: string;
|
|
31
|
+
describedBy?: string;
|
|
32
|
+
invalid?: boolean;
|
|
33
|
+
}): import("react").JSX.Element;
|
|
34
|
+
/**
|
|
35
|
+
* Several answers, all in sight, more than one allowed: the taxes of a product, the
|
|
36
|
+
* warehouses a user can see, the documents of an export.
|
|
37
|
+
*
|
|
38
|
+
* Context: it is React Aria's `ListBox` with `selectionMode="multiple"` and not a stack of
|
|
39
|
+
* checkboxes, because what it is to a keyboard is ONE stop with a list inside — Tab lands on
|
|
40
|
+
* it, the arrows move, Space toggles, Home and End jump, a letter finds the row — and twelve
|
|
41
|
+
* Tab stops is how a form becomes a chore. A disabled row is drawn and skipped. The value is
|
|
42
|
+
* a plain `string[]`, never React Aria's `Selection`.
|
|
43
|
+
*
|
|
44
|
+
* `mono` is for when the label was written by the machine — an account code, a tax code —
|
|
45
|
+
* and the detail was written by a person. The two faces are the point.
|
|
46
|
+
*/
|
|
47
|
+
export declare function CheckList({ label, items, value, onChange, mono, emptyLabel, describedBy: ownDescribedBy, mode, }: {
|
|
48
|
+
label: string;
|
|
49
|
+
items: readonly CheckItem[];
|
|
50
|
+
value: readonly string[];
|
|
51
|
+
onChange?: (next: string[]) => void;
|
|
52
|
+
mono?: boolean;
|
|
53
|
+
/** What read mode says when nothing is chosen. By default, the copy table's `field.none`. */
|
|
54
|
+
emptyLabel?: string;
|
|
55
|
+
describedBy?: string;
|
|
56
|
+
mode?: FormMode;
|
|
57
|
+
}): 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 { useUiCopy } from "../../foundations/copy.js";
|
|
4
|
+
import { useFieldIds } from "./context.js";
|
|
5
|
+
import { useFormMode } from "./mode.js";
|
|
6
|
+
import { Check, Minus } from "lucide-react";
|
|
7
|
+
import { useId } from "react";
|
|
8
|
+
import { Checkbox as AriaCheckbox, ListBox, ListBoxItem, Text, } from "react-aria-components";
|
|
9
|
+
function Mark({ on, mixed, focus = false, invalid = false, }) {
|
|
10
|
+
return (_jsx("span", { "aria-hidden": true, className: `inline-flex size-4 shrink-0 items-center justify-center rounded-mark border transition-colors duration-(--duration-instant) forced-color-adjust-none ${on
|
|
11
|
+
? "border-primary bg-primary text-surface forced-colors:border-[Highlight] forced-colors:bg-[Highlight] forced-colors:text-[HighlightText]"
|
|
12
|
+
: `bg-surface text-transparent ${invalid ? "border-danger" : "border-line-strong"} forced-colors:border-[ButtonBorder]`} ${focus ? "outline-2 outline-offset-2 outline-primary forced-colors:outline-[Highlight]" : ""}`, children: mixed ? (_jsx(Minus, { className: "size-3", strokeWidth: 2.5 })) : (_jsx(Check, { className: "size-3", strokeWidth: 2.5 })) }));
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* One yes/no answer of a form: «Exento de IVA», «Enviar la factura por correo al
|
|
16
|
+
* publicar».
|
|
17
|
+
*
|
|
18
|
+
* Context: it is React Aria's `Checkbox` — a native checkbox input inside the label, so the
|
|
19
|
+
* whole sentence is the click target, Space toggles it and the form reads it as a checkbox.
|
|
20
|
+
* `mixed` is its indeterminate state: the parent of a group where only some are on, and
|
|
21
|
+
* pressing it turns them ALL on — never all off, because «some» is closer to «all» than to
|
|
22
|
+
* «none» in what the person was doing.
|
|
23
|
+
*
|
|
24
|
+
* Inside a `Field` it is named by the field's label AND its own sentence, and described by
|
|
25
|
+
* the field's hint and error — the same wiring as `Switch`.
|
|
26
|
+
*/
|
|
27
|
+
export function Checkbox({ checked, mixed = false, onChange, label, disabled = false, mode, id: ownId, describedBy: ownDescribedBy, invalid: ownInvalid, }) {
|
|
28
|
+
const { id, labelledBy, describedBy, invalid } = useFieldIds({
|
|
29
|
+
id: ownId,
|
|
30
|
+
describedBy: ownDescribedBy,
|
|
31
|
+
invalid: ownInvalid,
|
|
32
|
+
});
|
|
33
|
+
const textId = useId();
|
|
34
|
+
const on = checked || mixed;
|
|
35
|
+
if (useFormMode(mode) === "read") {
|
|
36
|
+
return (_jsxs("span", { className: "flex min-h-(--size-control-md) items-center gap-2 text-meta text-ink", children: [_jsx(Mark, { on: on, mixed: mixed && !checked }), label] }));
|
|
37
|
+
}
|
|
38
|
+
return (_jsx(AriaCheckbox, { id: id, "aria-labelledby": labelledBy === undefined ? undefined : `${labelledBy} ${textId}`, "aria-describedby": describedBy, isSelected: checked, isIndeterminate: mixed, onChange: (next) => onChange?.(mixed ? true : next), isDisabled: disabled, isInvalid: invalid, className: "group inline-flex min-w-0 cursor-pointer items-center gap-2.5 text-meta text-ink disabled:cursor-not-allowed disabled:opacity-45", children: ({ isSelected, isIndeterminate, isFocusVisible, isInvalid }) => (_jsxs(_Fragment, { children: [_jsx(Mark, { on: isSelected || isIndeterminate, mixed: isIndeterminate, focus: isFocusVisible, invalid: isInvalid }), _jsx("span", { id: textId, className: "min-w-0", children: label })] })) }));
|
|
39
|
+
}
|
|
40
|
+
/** The chosen values, in the order the person picked them — what `onChange` hands back. */
|
|
41
|
+
function valuesOf(keys, items) {
|
|
42
|
+
if (keys === "all")
|
|
43
|
+
return items.filter((item) => !item.disabled).map((item) => item.value);
|
|
44
|
+
return [...keys].map(String);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Several answers, all in sight, more than one allowed: the taxes of a product, the
|
|
48
|
+
* warehouses a user can see, the documents of an export.
|
|
49
|
+
*
|
|
50
|
+
* Context: it is React Aria's `ListBox` with `selectionMode="multiple"` and not a stack of
|
|
51
|
+
* checkboxes, because what it is to a keyboard is ONE stop with a list inside — Tab lands on
|
|
52
|
+
* it, the arrows move, Space toggles, Home and End jump, a letter finds the row — and twelve
|
|
53
|
+
* Tab stops is how a form becomes a chore. A disabled row is drawn and skipped. The value is
|
|
54
|
+
* a plain `string[]`, never React Aria's `Selection`.
|
|
55
|
+
*
|
|
56
|
+
* `mono` is for when the label was written by the machine — an account code, a tax code —
|
|
57
|
+
* and the detail was written by a person. The two faces are the point.
|
|
58
|
+
*/
|
|
59
|
+
export function CheckList({ label, items, value, onChange, mono = false, emptyLabel, describedBy: ownDescribedBy, mode, }) {
|
|
60
|
+
const labelId = useId();
|
|
61
|
+
const { describedBy } = useFieldIds({ describedBy: ownDescribedBy });
|
|
62
|
+
const none = useUiCopy().field.none;
|
|
63
|
+
if (useFormMode(mode) === "read") {
|
|
64
|
+
const picked = items.filter((item) => value.includes(item.value));
|
|
65
|
+
return (_jsxs("div", { className: "flex min-w-0 flex-col gap-1", children: [_jsx("p", { className: "text-micro text-muted", children: label }), _jsx("p", { className: `text-meta text-ink ${mono ? "font-mono" : ""}`, children: picked.length === 0
|
|
66
|
+
? (emptyLabel ?? none)
|
|
67
|
+
: picked.map((item) => item.label).join(", ") })] }));
|
|
68
|
+
}
|
|
69
|
+
return (_jsxs("div", { className: "flex min-w-0 flex-col gap-1.5", children: [_jsx("p", { id: labelId, className: "text-micro text-muted", children: label }), _jsx(ListBox, { "aria-labelledby": labelId, "aria-describedby": describedBy, items: items, selectionMode: "multiple", selectedKeys: value, onSelectionChange: (keys) => onChange?.(valuesOf(keys, items)), disabledKeys: items.filter((item) => item.disabled).map((item) => item.value), shouldFocusWrap: true, className: "flex flex-col overflow-hidden rounded-box border border-line bg-surface outline-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary", children: (item) => (_jsx(ListBoxItem, { id: item.value, textValue: item.label, className: ({ isSelected, isFocusVisible, isDisabled }) => `flex min-w-0 cursor-pointer items-center gap-2.5 border-b border-line px-3 py-2 text-left outline-0 last:border-b-0 ${isFocusVisible
|
|
70
|
+
? "outline-2 -outline-offset-2 outline-primary forced-colors:outline-[Highlight]"
|
|
71
|
+
: ""} ${isDisabled ? "cursor-not-allowed opacity-45" : ""} ${isSelected ? "bg-primary/8" : "hover:bg-ink/4"}`, children: ({ isSelected }) => (_jsxs(_Fragment, { children: [_jsx(Mark, { on: isSelected, mixed: false }), _jsxs("span", { className: "flex min-w-0 flex-col", children: [_jsx(Text, { slot: "label", className: `truncate text-meta text-ink ${mono ? "font-mono" : ""}`, children: item.label }), item.detail === undefined ? null : (_jsx(Text, { slot: "description", className: "truncate text-micro text-muted", children: item.detail }))] })] })) })) })] }));
|
|
72
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type FormMode } from "./mode.js";
|
|
2
|
+
/**
|
|
3
|
+
* Drop a file, or pick one. Progress is a real bar; failure is a sentence.
|
|
4
|
+
*
|
|
5
|
+
* This is the control. Where it lives —a `Dialog`, a wizard step— is the
|
|
6
|
+
* consumer's. Putting a dashed box on top of a list was the product drawing
|
|
7
|
+
* the control in the wrong place, not the absence of a control.
|
|
8
|
+
*
|
|
9
|
+
* Context: the zone is React Aria's `DropZone` and the button its `FileTrigger`.
|
|
10
|
+
* Dragging is a mouse gesture, so the zone is never the only way in: the
|
|
11
|
+
* «choose file» button opens the system's file dialog from the keyboard, and the
|
|
12
|
+
* zone itself takes a paste (Ctrl/⌘+V of a copied file) and announces a drop to
|
|
13
|
+
* a screen reader. While a file uploads the zone refuses another one — a second
|
|
14
|
+
* drop in the middle of an upload replaces nothing and confuses everything.
|
|
15
|
+
*
|
|
16
|
+
* `accept` filters the file dialog. A drop is NOT filtered: the browser does not
|
|
17
|
+
* say a dragged file's extension until it lands, and a drop that silently does
|
|
18
|
+
* nothing reads as a broken zone. The file reaches `onFile`, and the consumer
|
|
19
|
+
* answers with `error` when it cannot read it.
|
|
20
|
+
*/
|
|
21
|
+
export type DropZoneProps = {
|
|
22
|
+
/** The browser's `accept` list: extensions and MIME types, comma-separated
|
|
23
|
+
* (`".pdf,application/pdf"`). */
|
|
24
|
+
accept?: string;
|
|
25
|
+
label?: string;
|
|
26
|
+
busy?: boolean;
|
|
27
|
+
/** 0 to 100, same scale as `ProgressBar`. */
|
|
28
|
+
progress?: number;
|
|
29
|
+
fileName?: string;
|
|
30
|
+
fileDetail?: string;
|
|
31
|
+
error?: string | null;
|
|
32
|
+
onFile: (file: File) => void;
|
|
33
|
+
onCancel?: () => void;
|
|
34
|
+
onRetry?: () => void;
|
|
35
|
+
id?: string;
|
|
36
|
+
describedBy?: string;
|
|
37
|
+
invalid?: boolean;
|
|
38
|
+
/** Forces the mode. By default it inherits the form's: in read mode it draws the
|
|
39
|
+
* file's name. */
|
|
40
|
+
mode?: FormMode;
|
|
41
|
+
};
|
|
42
|
+
export declare function DropZone({ accept, label, busy, progress, fileName, fileDetail, error, onFile, onCancel, onRetry, id: ownId, describedBy: ownDescribedBy, invalid: ownInvalid, mode, }: DropZoneProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useUiCopy } from "../../foundations/copy.js";
|
|
4
|
+
import { cn } from "../../foundations/variants.js";
|
|
5
|
+
import { Button, buttonClasses } from "../button/Button.js";
|
|
6
|
+
import { useFieldIds } from "./context.js";
|
|
7
|
+
import { ReadValue } from "./Field.js";
|
|
8
|
+
import { useFormMode } from "./mode.js";
|
|
9
|
+
import { Failed } from "../notice/Notice.js";
|
|
10
|
+
import { ProgressBar } from "../value/Value.js";
|
|
11
|
+
import { Button as AriaButton, DropZone as AriaDropZone, FileTrigger, } from "react-aria-components";
|
|
12
|
+
export function DropZone({ accept, label, busy = false, progress, fileName, fileDetail, error, onFile, onCancel, onRetry, id: ownId, describedBy: ownDescribedBy, invalid: ownInvalid, mode, }) {
|
|
13
|
+
const copy = useUiCopy();
|
|
14
|
+
const { id, labelledBy, describedBy, invalid } = useFieldIds({
|
|
15
|
+
id: ownId,
|
|
16
|
+
describedBy: ownDescribedBy,
|
|
17
|
+
invalid: ownInvalid,
|
|
18
|
+
});
|
|
19
|
+
const readMode = useFormMode(mode) === "read";
|
|
20
|
+
if (readMode)
|
|
21
|
+
return _jsx(ReadValue, { value: fileName ?? null });
|
|
22
|
+
const sentence = label ?? copy.field.drop;
|
|
23
|
+
const types = accept
|
|
24
|
+
?.split(",")
|
|
25
|
+
.map((t) => t.trim())
|
|
26
|
+
.filter((t) => t !== "");
|
|
27
|
+
async function onDrop(items) {
|
|
28
|
+
if (busy)
|
|
29
|
+
return;
|
|
30
|
+
for (const item of items) {
|
|
31
|
+
if (item.kind === "file") {
|
|
32
|
+
onFile(await item.getFile());
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return (_jsxs("div", { className: "flex min-w-0 flex-col gap-2", children: [_jsxs(AriaDropZone, { ...(labelledBy === undefined
|
|
38
|
+
? { "aria-label": sentence }
|
|
39
|
+
: { "aria-labelledby": labelledBy }), "aria-describedby": describedBy, isDisabled: busy, getDropOperation: () => (busy ? "cancel" : "copy"), onDrop: (event) => void onDrop(event.items), className: ({ isDropTarget, isFocusVisible }) => cn("flex flex-col items-center gap-3 rounded-box border px-4 py-6 text-center transition-colors duration-(--duration-instant) outline-primary outline-offset-2", isDropTarget
|
|
40
|
+
? "border-primary bg-primary/8 forced-colors:border-[Highlight]"
|
|
41
|
+
: invalid
|
|
42
|
+
? "border-danger bg-surface"
|
|
43
|
+
: "border-line-strong bg-surface", isFocusVisible ? "outline-2" : "outline-0"), children: [_jsx("p", { className: "text-body text-muted", children: sentence }), busy ? (_jsxs("div", { className: "flex w-full max-w-sm flex-col items-center gap-2", children: [fileName === undefined ? null : (_jsx("p", { className: "max-w-full truncate text-body text-ink", children: fileName })), _jsx("div", { className: "w-full", children: _jsx(ProgressBar, { value: progress ?? 0, label: copy.field.uploadProgress, width: "w-full" }) }), fileDetail === undefined ? null : (_jsx("p", { className: "text-meta text-muted", children: fileDetail }))] })) : (_jsxs("div", { className: "flex flex-wrap items-center justify-center gap-3", children: [_jsx(FileTrigger, { ...(types === undefined ? {} : { acceptedFileTypes: types }), onSelect: (files) => {
|
|
44
|
+
const file = files?.[0];
|
|
45
|
+
if (file !== undefined)
|
|
46
|
+
onFile(file);
|
|
47
|
+
}, children: _jsx(AriaButton, { id: id, "data-emphasis": "secondary", className: buttonClasses("secondary", "md"), children: copy.field.chooseFile }) }), onCancel === undefined ? null : (_jsx(Button, { role: "ghost", onClick: onCancel, children: copy.field.cancel }))] }))] }), error === undefined || error === null ? null : (_jsx(Failed, { heading: copy.field.fileUnreadable, sentence: error, ...(onRetry === undefined
|
|
48
|
+
? {}
|
|
49
|
+
: {
|
|
50
|
+
action: (_jsx(Button, { role: "ghost", onClick: onRetry, children: copy.field.tryAnother })),
|
|
51
|
+
}) }))] }));
|
|
52
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { type FieldIds } from "./context.js";
|
|
2
|
+
import { type FormMode } from "./mode.js";
|
|
3
|
+
import { type ReactNode } from "react";
|
|
4
|
+
export declare function Field({ label, hint, error, required, children, htmlFor, layout, action, mode, }: {
|
|
5
|
+
label: string;
|
|
6
|
+
/** What is expected. A `?` on the label that opens it on press. */
|
|
7
|
+
hint?: string;
|
|
8
|
+
/** What has to be corrected. It goes BELOW, close to the cursor, and it says
|
|
9
|
+
* what to put. */
|
|
10
|
+
error?: string;
|
|
11
|
+
required?: boolean;
|
|
12
|
+
/** The control. Written directly — `<Field label="Nombre"><TextInput … /></Field>` — it
|
|
13
|
+
* reads its id, description and error state from the field. The render prop
|
|
14
|
+
* `{(aria) => …}` is still there for a control that is not one of the system's. */
|
|
15
|
+
children: ReactNode | ((ids: FieldIds) => ReactNode);
|
|
16
|
+
htmlFor?: string;
|
|
17
|
+
/** `inline` puts the label on the LEFT, in a fixed-width column. It is what
|
|
18
|
+
* turns twenty stacked fields into a record that reads at a glance: every
|
|
19
|
+
* value starts on the same vertical, so the eye goes down a column instead of
|
|
20
|
+
* bouncing. It needs width — in a narrow drawer, the label eats the field.
|
|
21
|
+
* Omit it to inherit the sheet: `horizontal` there is this, `stacked` is the
|
|
22
|
+
* label above. */
|
|
23
|
+
layout?: "stacked" | "inline";
|
|
24
|
+
/** A button attached to the label: "generate", "clear", "pick from the
|
|
25
|
+
* catalog". It goes above and not below the control because it belongs to the
|
|
26
|
+
* FIELD, not to the form — what goes below is what closes the whole form. */
|
|
27
|
+
action?: ReactNode;
|
|
28
|
+
/** Forces the mode of this field. By default it inherits it from the view. */
|
|
29
|
+
mode?: FormMode;
|
|
30
|
+
}): import("react").JSX.Element;
|
|
31
|
+
/**
|
|
32
|
+
* A label with its VALUE, without a control below. What was missing to show a
|
|
33
|
+
* record without pretending it is being edited.
|
|
34
|
+
*
|
|
35
|
+
* Context: until now, showing a value with its name in a form forced you to put
|
|
36
|
+
* a disabled `TextInput`. That draws the box, the border and the height of
|
|
37
|
+
* something you write into, and a whole record like that is twelve boxes empty
|
|
38
|
+
* of meaning.
|
|
39
|
+
*
|
|
40
|
+
* It uses the same geometry as `Field`, so a field that goes from being looked
|
|
41
|
+
* at to being edited does not move anything around it.
|
|
42
|
+
*/
|
|
43
|
+
export declare function DataField({ label, value, hint, layout, mono, action, }: {
|
|
44
|
+
label: string;
|
|
45
|
+
/** `undefined` and `null` are drawn with the missing mark: saying there is no
|
|
46
|
+
* data is information, and a blank line does not give it. */
|
|
47
|
+
value: ReactNode;
|
|
48
|
+
hint?: string;
|
|
49
|
+
layout?: "stacked" | "inline";
|
|
50
|
+
/** The value was written by the machine: an id, a code, an account number. */
|
|
51
|
+
mono?: boolean;
|
|
52
|
+
action?: ReactNode;
|
|
53
|
+
}): import("react").JSX.Element;
|
|
54
|
+
/** What a control draws when the form is LOOKED AT instead of written into.
|
|
55
|
+
*
|
|
56
|
+
* It is a single piece and not an `if` repeated in every control: if each one
|
|
57
|
+
* solves its read mode its own way, the record ends up with six different ways
|
|
58
|
+
* of saying the same thing. */
|
|
59
|
+
export declare function ReadValue({ value, mono }: {
|
|
60
|
+
value: ReactNode;
|
|
61
|
+
mono?: boolean;
|
|
62
|
+
}): import("react").JSX.Element;
|
|
63
|
+
/** The shared look of everything that is written into or chosen from.
|
|
64
|
+
*
|
|
65
|
+
* It exists as a function and not as a loose class so that a new control cannot
|
|
66
|
+
* be born with another height, another radius or another focus. That was the
|
|
67
|
+
* original defect: every file wrote its own `<input className="…">`. */
|
|
68
|
+
export declare function controlClasses(invalid?: boolean): string;
|
|
69
|
+
/** What every text control shares: the field's wiring, overridable prop by prop. */
|
|
70
|
+
type ControlProps = {
|
|
71
|
+
id?: string;
|
|
72
|
+
describedBy?: string;
|
|
73
|
+
invalid?: boolean;
|
|
74
|
+
disabled?: boolean;
|
|
75
|
+
/** Forces the mode. By default it inherits the form's: in read mode it draws the value. */
|
|
76
|
+
mode?: FormMode;
|
|
77
|
+
/** The name of the control when it is NOT inside a `Field` — a cell of an editable table,
|
|
78
|
+
* the box of a message. Inside a field, the field's label names it. */
|
|
79
|
+
"aria-label"?: string;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* One line of text: a business name, a reference, a NIT.
|
|
83
|
+
*
|
|
84
|
+
* Context: it is React Aria's `TextField` — the field's label, hint and error reach the real
|
|
85
|
+
* `<input>` through `useFieldIds`, and React Aria adds the rest of what a text input owes a
|
|
86
|
+
* screen reader. What it draws is `controlClasses()`, the one box every control shares.
|
|
87
|
+
*/
|
|
88
|
+
export declare function TextInput({ id: ownId, value, onChange, placeholder, describedBy: ownDescribedBy, invalid: ownInvalid, disabled, mode, mono, spellCheck, "aria-label": ariaLabel, }: ControlProps & {
|
|
89
|
+
value: string;
|
|
90
|
+
onChange?: (next: string) => void;
|
|
91
|
+
placeholder?: string;
|
|
92
|
+
/** The value was written by the machine and gets compared with the one next to it: an
|
|
93
|
+
* id, a key, an account code. See the family rule in Typography. */
|
|
94
|
+
mono?: boolean;
|
|
95
|
+
/** `false` for a value that is a token and not prose — a product code, a reference, an
|
|
96
|
+
* account code — so the browser stops underlining identifiers as misspellings. */
|
|
97
|
+
spellCheck?: boolean;
|
|
98
|
+
}): import("react").JSX.Element;
|
|
99
|
+
/**
|
|
100
|
+
* A number typed the way the person writes it: «1.234,56» in Bolivia, «1,234.56» in the
|
|
101
|
+
* United States.
|
|
102
|
+
*
|
|
103
|
+
* Context: it was an `<input type="number">`, and that input only understands the
|
|
104
|
+
* browser's idea of a number — on a Spanish form, «1.234,56» was either refused or read as
|
|
105
|
+
* one point two. It is React Aria's `NumberField` now, inside `AriaLocale`: it parses and
|
|
106
|
+
* formats in the locale of the copy table, not the browser's, so an amount reads the same
|
|
107
|
+
* here as in the listing next to it. `format` takes `Intl.NumberFormatOptions` — a
|
|
108
|
+
* currency, a percentage, two fixed decimals — and the field types and shows it that way.
|
|
109
|
+
*
|
|
110
|
+
* The arrow keys (and the two small buttons) step by `step`; on leaving the field the value
|
|
111
|
+
* is clamped to `min`/`max` and snapped to `step`, so what the form submits is always a
|
|
112
|
+
* number the field would have accepted. The value stays a plain `number`, and `""` is the
|
|
113
|
+
* empty field — never `NaN`, which is how React Aria says it inside.
|
|
114
|
+
*/
|
|
115
|
+
export declare function NumberInput({ id: ownId, value, onChange, min, max, step, unit, format, placeholder, describedBy: ownDescribedBy, invalid: ownInvalid, disabled, mode, "aria-label": ariaLabel, }: ControlProps & {
|
|
116
|
+
value: number | "";
|
|
117
|
+
onChange?: (next: number | "") => void;
|
|
118
|
+
min?: number;
|
|
119
|
+
max?: number;
|
|
120
|
+
step?: number;
|
|
121
|
+
/** The unit, attached to the field: "días", "u.", "kg". A number without a unit in a
|
|
122
|
+
* form is half a question. For money, prefer `format` with a currency. */
|
|
123
|
+
unit?: string;
|
|
124
|
+
/** How the number is written: `{ style: "currency", currency: "BOB" }`,
|
|
125
|
+
* `{ style: "percent" }`, `{ minimumFractionDigits: 2 }`. In the copy's locale. */
|
|
126
|
+
format?: Intl.NumberFormatOptions;
|
|
127
|
+
placeholder?: string;
|
|
128
|
+
}): import("react").JSX.Element;
|
|
129
|
+
/**
|
|
130
|
+
* Several lines of prose: the notes of an order, the reason of a credit note.
|
|
131
|
+
*
|
|
132
|
+
* Context: React Aria's `TextField` with a `TextArea` inside — the same wiring as
|
|
133
|
+
* `TextInput`, the same box, taller and resizable only downwards, because a note grows in
|
|
134
|
+
* lines, not in width.
|
|
135
|
+
*/
|
|
136
|
+
export declare function TextArea({ id: ownId, value, onChange, placeholder, rows, describedBy: ownDescribedBy, invalid: ownInvalid, disabled, mode, "aria-label": ariaLabel, }: ControlProps & {
|
|
137
|
+
value: string;
|
|
138
|
+
onChange?: (next: string) => void;
|
|
139
|
+
placeholder?: string;
|
|
140
|
+
rows?: number;
|
|
141
|
+
}): import("react").JSX.Element;
|
|
142
|
+
export interface SelectOption {
|
|
143
|
+
value: string;
|
|
144
|
+
label: string;
|
|
145
|
+
disabled?: boolean;
|
|
146
|
+
}
|
|
147
|
+
/** The system's select, for SHORT and closed lists: the payment terms, the currency, the
|
|
148
|
+
* warehouse.
|
|
149
|
+
*
|
|
150
|
+
* Context: it was a native `<select>`, and its open list was the operating system's — its
|
|
151
|
+
* font, its colors, its radius, in the middle of a form drawn with the system's. It is
|
|
152
|
+
* React Aria's `Select` now: the trigger is the system's control box and the list floats
|
|
153
|
+
* on the system's `Popover`, row for row the same as `ComboBox`'s. The keyboard is still a
|
|
154
|
+
* select's: arrows open it and move, typing a letter jumps to the option, Enter picks.
|
|
155
|
+
*
|
|
156
|
+
* As soon as the list has to show two pieces of data per option —a name and its tax id—
|
|
157
|
+
* or grows past a screen, it is a `ComboBox` (type to find) or a `Picker` (records with
|
|
158
|
+
* their metadata). An option here is ONE LINE OF TEXT. */
|
|
159
|
+
export declare function Select({ id: ownId, value, options, onChange, describedBy: ownDescribedBy, invalid: ownInvalid, disabled, placeholder, mode, "aria-label": ariaLabel, }: ControlProps & {
|
|
160
|
+
/** The chosen option's `value`; `""` is nothing chosen, and draws the placeholder. */
|
|
161
|
+
value: string;
|
|
162
|
+
options: readonly SelectOption[];
|
|
163
|
+
onChange?: (next: string) => void;
|
|
164
|
+
placeholder?: string;
|
|
165
|
+
}): import("react").JSX.Element;
|
|
166
|
+
export {};
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs as _jsxs, Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { AriaLocale } from "../../foundations/aria.js";
|
|
4
|
+
import { useUiCopy } from "../../foundations/copy.js";
|
|
5
|
+
import { byDensity, useDensity } from "../../foundations/density.js";
|
|
6
|
+
import { focusRing, tv } from "../../foundations/variants.js";
|
|
7
|
+
import { FieldGroup, listOptionStyles } from "./aria.js";
|
|
8
|
+
import { FieldIdsContext, useFieldIds, } from "./context.js";
|
|
9
|
+
import { useSheetLayout } from "./layout.js";
|
|
10
|
+
import { FormModeProvider, useFormMode, } from "./mode.js";
|
|
11
|
+
import { Hint } from "../hint/Hint.js";
|
|
12
|
+
import { Popover } from "../overlay/Popover.js";
|
|
13
|
+
import { AlertCircle, Check, ChevronDown, ChevronUp } from "lucide-react";
|
|
14
|
+
import { useId } from "react";
|
|
15
|
+
import { Button as AriaButton, Input, ListBox, ListBoxItem, NumberField as AriaNumberField, Select as AriaSelect, SelectValue, TextArea as AriaTextArea, TextField as AriaTextField, } from "react-aria-components";
|
|
16
|
+
/* ==================================================================== *
|
|
17
|
+
* THE CONTROLS — the label is not optional and the error is not a color.
|
|
18
|
+
*
|
|
19
|
+
* The state we came from is on record: twenty raw controls across six files,
|
|
20
|
+
* **none of them with an associated label or `aria-describedby`**, and all of
|
|
21
|
+
* them 24 px tall. That is not a matter of abstract accessibility: a `label`
|
|
22
|
+
* without `htmlFor` is a label that does not enlarge the click area, and a hint
|
|
23
|
+
* that is not linked is a hint a screen reader never reads.
|
|
24
|
+
*
|
|
25
|
+
* ── The error says WHAT to do, not that something is wrong ──────────
|
|
26
|
+
*
|
|
27
|
+
* "Invalid value" is no use. An error message has to say what was expected
|
|
28
|
+
* —"it has to be a number between 0 and 1"— because whoever made the mistake
|
|
29
|
+
* already knows they made it: what they don't know is what to put.
|
|
30
|
+
*
|
|
31
|
+
* And it is never ONLY color: it carries an icon and text. A red border is
|
|
32
|
+
* invisible to someone who doesn't tell red apart, and also to someone who is
|
|
33
|
+
* looking at another part of the screen.
|
|
34
|
+
*
|
|
35
|
+
* ── The hint sits on the label, the error BELOW the control ─────────
|
|
36
|
+
*
|
|
37
|
+
* Help is a `?` next to the name, opened with a press. A paragraph above the
|
|
38
|
+
* control ate a line on every field. The error still goes below, close to
|
|
39
|
+
* the cursor, and it says what to put.
|
|
40
|
+
* ==================================================================== */
|
|
41
|
+
export function Field({ label, hint, error, required = false, children, htmlFor, layout, action, mode, }) {
|
|
42
|
+
const d = useDensity();
|
|
43
|
+
const copy = useUiCopy();
|
|
44
|
+
const auto = useId();
|
|
45
|
+
const id = htmlFor ?? auto;
|
|
46
|
+
const sheetLayout = useSheetLayout();
|
|
47
|
+
const inline = layout === "inline" || (layout === undefined && sheetLayout === "horizontal");
|
|
48
|
+
const hintId = `${id}-hint`;
|
|
49
|
+
const errorId = `${id}-error`;
|
|
50
|
+
const describedBy = [hint === undefined ? null : hintId, error === undefined ? null : errorId]
|
|
51
|
+
.filter((x) => x !== null)
|
|
52
|
+
.join(" ") || undefined;
|
|
53
|
+
const labelNode = (_jsxs("span", { className: `flex min-w-0 items-center gap-1 text-micro font-medium text-muted ${inline ? "pt-2" : ""}`, children: [_jsxs("label", { id: `${id}-label`, htmlFor: id, className: inline ? "min-w-0 truncate" : "", children: [label, required ? (_jsxs(_Fragment, { children: [_jsxs("span", { className: "text-ink", "aria-hidden": true, children: [" ", "*"] }), _jsxs("span", { className: "sr-only", children: [", ", copy.field.required] })] })) : null] }), hint === undefined ? null : _jsx(Hint, { id: hintId, children: hint })] }));
|
|
54
|
+
/* The field's `mode` propagates INWARDS and is not used here: what knows how
|
|
55
|
+
to draw itself as data is the control, not the label. That way a computed
|
|
56
|
+
field stays in read mode inside a form that is being edited, without the
|
|
57
|
+
consumer having to pass it to the control as well. */
|
|
58
|
+
const ids = {
|
|
59
|
+
id,
|
|
60
|
+
labelId: `${id}-label`,
|
|
61
|
+
describedBy,
|
|
62
|
+
invalid: error !== undefined,
|
|
63
|
+
};
|
|
64
|
+
/* The render prop gets the same context as a control written directly: a system control
|
|
65
|
+
inside it then also finds the label that names it, not only the id it is handed. */
|
|
66
|
+
const content = (_jsx(FieldIdsContext.Provider, { value: ids, children: typeof children === "function" ? children(ids) : children }));
|
|
67
|
+
const body = (_jsxs(_Fragment, { children: [mode === undefined ? (content) : (_jsx(FormModeProvider, { value: mode, children: content })), error === undefined ? null : (_jsxs("p", { id: errorId, role: "alert", className: "flex items-start gap-1.5 text-micro text-danger", children: [_jsx(AlertCircle, { className: "mt-px size-(--size-icon-sm) shrink-0", strokeWidth: 2, "aria-hidden": true }), _jsx("span", { className: "max-w-prose", children: error })] }))] }));
|
|
68
|
+
if (inline) {
|
|
69
|
+
return (_jsxs("div", { className: "grid-label grid min-w-0 items-start gap-x-4 gap-y-1", children: [labelNode, _jsx("div", { className: "flex min-w-0 flex-col gap-1", children: body }), action === undefined ? null : (_jsx("div", { "data-under-control": true, className: "flex items-center gap-2", children: action }))] }));
|
|
70
|
+
}
|
|
71
|
+
return (_jsxs("div", { className: `flex min-w-0 flex-col ${byDensity(d, "gap-1", "gap-0.5")}`, children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [labelNode, action === undefined ? null : _jsx("span", { className: "ml-auto shrink-0", children: action })] }), body] }));
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* A label with its VALUE, without a control below. What was missing to show a
|
|
75
|
+
* record without pretending it is being edited.
|
|
76
|
+
*
|
|
77
|
+
* Context: until now, showing a value with its name in a form forced you to put
|
|
78
|
+
* a disabled `TextInput`. That draws the box, the border and the height of
|
|
79
|
+
* something you write into, and a whole record like that is twelve boxes empty
|
|
80
|
+
* of meaning.
|
|
81
|
+
*
|
|
82
|
+
* It uses the same geometry as `Field`, so a field that goes from being looked
|
|
83
|
+
* at to being edited does not move anything around it.
|
|
84
|
+
*/
|
|
85
|
+
export function DataField({ label, value, hint, layout, mono = false, action, }) {
|
|
86
|
+
const d = useDensity();
|
|
87
|
+
const sheetLayout = useSheetLayout();
|
|
88
|
+
const inline = layout === "inline" || (layout === undefined && sheetLayout === "horizontal");
|
|
89
|
+
const empty = value === undefined || value === null || value === "";
|
|
90
|
+
const unset = useUiCopy().field.unset;
|
|
91
|
+
const labelNode = (_jsxs("span", { className: `flex min-w-0 items-center gap-1 text-micro font-medium text-muted ${inline ? "pt-0.5" : ""}`, children: [_jsx("span", { className: inline ? "min-w-0 truncate" : "", children: label }), hint === undefined ? null : _jsx(Hint, { children: hint })] }));
|
|
92
|
+
const valueNode = (_jsx("span", { className: `min-w-0 text-meta ${empty ? "text-muted italic" : "text-ink"} ${mono ? "font-mono tabular-nums" : ""}`, children: empty ? unset : value }));
|
|
93
|
+
if (inline) {
|
|
94
|
+
return (_jsxs("div", { className: "grid-label grid min-w-0 items-baseline gap-x-4 gap-y-0.5", children: [labelNode, _jsxs("span", { className: "flex min-w-0 items-baseline gap-2", children: [valueNode, action] })] }));
|
|
95
|
+
}
|
|
96
|
+
return (_jsxs("div", { className: `flex min-w-0 flex-col ${byDensity(d, "gap-1", "gap-0.5")}`, children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [labelNode, action === undefined ? null : _jsx("span", { className: "ml-auto shrink-0", children: action })] }), valueNode] }));
|
|
97
|
+
}
|
|
98
|
+
/** What a control draws when the form is LOOKED AT instead of written into.
|
|
99
|
+
*
|
|
100
|
+
* It is a single piece and not an `if` repeated in every control: if each one
|
|
101
|
+
* solves its read mode its own way, the record ends up with six different ways
|
|
102
|
+
* of saying the same thing. */
|
|
103
|
+
export function ReadValue({ value, mono = false }) {
|
|
104
|
+
const unset = useUiCopy().field.unset;
|
|
105
|
+
const empty = value === undefined || value === null || value === "";
|
|
106
|
+
return (_jsx("span", { className: `flex min-h-(--size-control-md) min-w-0 items-center text-meta ${empty ? "text-muted italic" : "text-ink"} ${mono ? "font-mono tabular-nums" : ""}`, children: empty ? unset : value }));
|
|
107
|
+
}
|
|
108
|
+
/** The shared look of everything that is written into or chosen from.
|
|
109
|
+
*
|
|
110
|
+
* It exists as a function and not as a loose class so that a new control cannot
|
|
111
|
+
* be born with another height, another radius or another focus. That was the
|
|
112
|
+
* original defect: every file wrote its own `<input className="…">`. */
|
|
113
|
+
export function controlClasses(invalid = false) {
|
|
114
|
+
return `h-(--size-control-md) w-full min-w-0 rounded-control border bg-surface px-2.5 text-meta text-ink transition-colors duration-(--duration-instant) placeholder:text-muted focus-visible:outline-2 focus-visible:outline-offset-2 disabled:cursor-not-allowed disabled:bg-ground disabled:text-muted ${invalid
|
|
115
|
+
? "border-danger focus-visible:border-danger focus-visible:outline-danger"
|
|
116
|
+
: "border-line-strong focus-visible:border-primary focus-visible:outline-primary"}`;
|
|
117
|
+
}
|
|
118
|
+
/* `aria-describedby` goes on the INPUT and not on React Aria's field: the field adds the ids
|
|
119
|
+
of its own description and error slots, which this system never renders (`Field` draws
|
|
120
|
+
them), and the input's own prop replaces that list with the field's real one. */
|
|
121
|
+
/** The accessible name of a control. Inside a `Field` the field's label names it; outside,
|
|
122
|
+
* the consumer's `aria-label`, and as a last resort the placeholder — which is what a
|
|
123
|
+
* browser reads for a bare `<input placeholder>` anyway, so a control outside a form is
|
|
124
|
+
* never nameless. */
|
|
125
|
+
function nameOf(labelledBy, ariaLabel, placeholder) {
|
|
126
|
+
return labelledBy === undefined ? (ariaLabel ?? placeholder) : ariaLabel;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* One line of text: a business name, a reference, a NIT.
|
|
130
|
+
*
|
|
131
|
+
* Context: it is React Aria's `TextField` — the field's label, hint and error reach the real
|
|
132
|
+
* `<input>` through `useFieldIds`, and React Aria adds the rest of what a text input owes a
|
|
133
|
+
* screen reader. What it draws is `controlClasses()`, the one box every control shares.
|
|
134
|
+
*/
|
|
135
|
+
export function TextInput({ id: ownId, value, onChange, placeholder, describedBy: ownDescribedBy, invalid: ownInvalid, disabled = false, mode, mono = false, spellCheck, "aria-label": ariaLabel, }) {
|
|
136
|
+
const { id, labelledBy, describedBy, invalid } = useFieldIds({
|
|
137
|
+
id: ownId,
|
|
138
|
+
describedBy: ownDescribedBy,
|
|
139
|
+
invalid: ownInvalid,
|
|
140
|
+
});
|
|
141
|
+
if (useFormMode(mode) === "read")
|
|
142
|
+
return _jsx(ReadValue, { value: value, mono: mono });
|
|
143
|
+
return (_jsx(AriaTextField, { id: id, "aria-labelledby": labelledBy, "aria-label": nameOf(labelledBy, ariaLabel, placeholder), value: value, onChange: (next) => onChange?.(next), isInvalid: invalid, isDisabled: disabled, className: "w-full min-w-0", children: _jsx(Input, { "aria-describedby": describedBy, placeholder: placeholder, spellCheck: spellCheck, className: `${controlClasses(invalid)} ${mono ? "font-mono tabular-nums" : ""}` }) }));
|
|
144
|
+
}
|
|
145
|
+
/** The amount, the quantity, the percentage — read in the copy's locale, every time. */
|
|
146
|
+
function useReadNumber(format) {
|
|
147
|
+
const { locale } = useUiCopy();
|
|
148
|
+
const formatter = new Intl.NumberFormat(locale, format);
|
|
149
|
+
return (value) => formatter.format(value);
|
|
150
|
+
}
|
|
151
|
+
const stepper = "flex min-h-0 flex-1 cursor-default items-center justify-center px-1 text-muted transition-colors duration-(--duration-instant) outline-0 hover:bg-ink/6 hover:text-ink pressed:bg-ink/10 disabled:cursor-not-allowed disabled:text-line-strong disabled:hover:bg-transparent forced-colors:disabled:text-[GrayText]";
|
|
152
|
+
/**
|
|
153
|
+
* A number typed the way the person writes it: «1.234,56» in Bolivia, «1,234.56» in the
|
|
154
|
+
* United States.
|
|
155
|
+
*
|
|
156
|
+
* Context: it was an `<input type="number">`, and that input only understands the
|
|
157
|
+
* browser's idea of a number — on a Spanish form, «1.234,56» was either refused or read as
|
|
158
|
+
* one point two. It is React Aria's `NumberField` now, inside `AriaLocale`: it parses and
|
|
159
|
+
* formats in the locale of the copy table, not the browser's, so an amount reads the same
|
|
160
|
+
* here as in the listing next to it. `format` takes `Intl.NumberFormatOptions` — a
|
|
161
|
+
* currency, a percentage, two fixed decimals — and the field types and shows it that way.
|
|
162
|
+
*
|
|
163
|
+
* The arrow keys (and the two small buttons) step by `step`; on leaving the field the value
|
|
164
|
+
* is clamped to `min`/`max` and snapped to `step`, so what the form submits is always a
|
|
165
|
+
* number the field would have accepted. The value stays a plain `number`, and `""` is the
|
|
166
|
+
* empty field — never `NaN`, which is how React Aria says it inside.
|
|
167
|
+
*/
|
|
168
|
+
export function NumberInput({ id: ownId, value, onChange, min, max, step, unit, format, placeholder, describedBy: ownDescribedBy, invalid: ownInvalid, disabled = false, mode, "aria-label": ariaLabel, }) {
|
|
169
|
+
const { id, labelledBy, describedBy, invalid } = useFieldIds({
|
|
170
|
+
id: ownId,
|
|
171
|
+
describedBy: ownDescribedBy,
|
|
172
|
+
invalid: ownInvalid,
|
|
173
|
+
});
|
|
174
|
+
const read = useReadNumber(format);
|
|
175
|
+
if (useFormMode(mode) === "read") {
|
|
176
|
+
return (_jsx(ReadValue, { value: value === "" ? null : (_jsxs("span", { className: "tabular-nums", children: [read(value), unit === undefined ? "" : ` ${unit}`] })) }));
|
|
177
|
+
}
|
|
178
|
+
return (_jsx(AriaLocale, { children: _jsxs(AriaNumberField, { id: id, "aria-labelledby": labelledBy, "aria-label": nameOf(labelledBy, ariaLabel, placeholder), value: value === "" ? Number.NaN : value, onChange: (next) => onChange?.(Number.isNaN(next) ? "" : next), minValue: min, maxValue: max, step: step, formatOptions: format, isInvalid: invalid, isDisabled: disabled, className: "flex w-full min-w-0 items-center gap-2", children: [_jsxs(FieldGroup, { children: [_jsx(Input, { "aria-describedby": describedBy, placeholder: placeholder, className: "h-full min-w-0 flex-1 bg-transparent px-2.5 text-meta text-ink tabular-nums outline-0 placeholder:text-muted disabled:cursor-not-allowed" }), _jsxs("span", { className: "flex h-full shrink-0 flex-col border-l border-line", children: [_jsx(AriaButton, { slot: "increment", className: stepper, children: _jsx(ChevronUp, { "aria-hidden": true, className: "size-(--size-icon-sm)", strokeWidth: 2 }) }), _jsx(AriaButton, { slot: "decrement", className: `${stepper} border-t border-line`, children: _jsx(ChevronDown, { "aria-hidden": true, className: "size-(--size-icon-sm)", strokeWidth: 2 }) })] })] }), unit === undefined ? null : (_jsx("span", { className: "shrink-0 text-meta whitespace-nowrap text-muted", children: unit }))] }) }));
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Several lines of prose: the notes of an order, the reason of a credit note.
|
|
182
|
+
*
|
|
183
|
+
* Context: React Aria's `TextField` with a `TextArea` inside — the same wiring as
|
|
184
|
+
* `TextInput`, the same box, taller and resizable only downwards, because a note grows in
|
|
185
|
+
* lines, not in width.
|
|
186
|
+
*/
|
|
187
|
+
export function TextArea({ id: ownId, value, onChange, placeholder, rows = 3, describedBy: ownDescribedBy, invalid: ownInvalid, disabled = false, mode, "aria-label": ariaLabel, }) {
|
|
188
|
+
const { id, labelledBy, describedBy, invalid } = useFieldIds({
|
|
189
|
+
id: ownId,
|
|
190
|
+
describedBy: ownDescribedBy,
|
|
191
|
+
invalid: ownInvalid,
|
|
192
|
+
});
|
|
193
|
+
/* The paragraph in read mode keeps its line breaks: a long text without them
|
|
194
|
+
is a block, and whoever wrote it put them there for a reason. */
|
|
195
|
+
if (useFormMode(mode) === "read") {
|
|
196
|
+
return value === "" ? (_jsx(ReadValue, { value: null })) : (_jsx("span", { className: "text-meta whitespace-pre-line text-ink", children: value }));
|
|
197
|
+
}
|
|
198
|
+
return (_jsx(AriaTextField, { id: id, "aria-labelledby": labelledBy, "aria-label": nameOf(labelledBy, ariaLabel, placeholder), value: value, onChange: (next) => onChange?.(next), isInvalid: invalid, isDisabled: disabled, className: "w-full min-w-0", children: _jsx(AriaTextArea, { "aria-describedby": describedBy, rows: rows, placeholder: placeholder, className: `${controlClasses(invalid)} h-auto resize-y py-1.5 leading-relaxed` }) }));
|
|
199
|
+
}
|
|
200
|
+
const selectTrigger = tv({
|
|
201
|
+
extend: focusRing,
|
|
202
|
+
base: "flex h-(--size-control-md) w-full min-w-0 cursor-default items-center gap-2 rounded-control border bg-surface pr-2 pl-2.5 text-start text-meta text-ink transition-colors duration-(--duration-instant) forced-colors:bg-[Field]",
|
|
203
|
+
variants: {
|
|
204
|
+
isFocusVisible: {
|
|
205
|
+
true: "border-primary",
|
|
206
|
+
},
|
|
207
|
+
isInvalid: {
|
|
208
|
+
false: "border-line-strong forced-colors:border-[ButtonBorder]",
|
|
209
|
+
true: "border-danger outline-danger forced-colors:border-[Mark]",
|
|
210
|
+
},
|
|
211
|
+
isDisabled: {
|
|
212
|
+
true: "cursor-not-allowed bg-ground text-muted forced-colors:text-[GrayText]",
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
/** The system's select, for SHORT and closed lists: the payment terms, the currency, the
|
|
217
|
+
* warehouse.
|
|
218
|
+
*
|
|
219
|
+
* Context: it was a native `<select>`, and its open list was the operating system's — its
|
|
220
|
+
* font, its colors, its radius, in the middle of a form drawn with the system's. It is
|
|
221
|
+
* React Aria's `Select` now: the trigger is the system's control box and the list floats
|
|
222
|
+
* on the system's `Popover`, row for row the same as `ComboBox`'s. The keyboard is still a
|
|
223
|
+
* select's: arrows open it and move, typing a letter jumps to the option, Enter picks.
|
|
224
|
+
*
|
|
225
|
+
* As soon as the list has to show two pieces of data per option —a name and its tax id—
|
|
226
|
+
* or grows past a screen, it is a `ComboBox` (type to find) or a `Picker` (records with
|
|
227
|
+
* their metadata). An option here is ONE LINE OF TEXT. */
|
|
228
|
+
export function Select({ id: ownId, value, options, onChange, describedBy: ownDescribedBy, invalid: ownInvalid, disabled = false, placeholder, mode, "aria-label": ariaLabel, }) {
|
|
229
|
+
const { id, labelledBy, describedBy, invalid } = useFieldIds({
|
|
230
|
+
id: ownId,
|
|
231
|
+
describedBy: ownDescribedBy,
|
|
232
|
+
invalid: ownInvalid,
|
|
233
|
+
});
|
|
234
|
+
/* In read mode the option's LABEL is drawn, not its value: `CONTADO` is the backend's
|
|
235
|
+
key, and what the form promised to show was «Al contado». */
|
|
236
|
+
if (useFormMode(mode) === "read") {
|
|
237
|
+
return _jsx(ReadValue, { value: options.find((o) => o.value === value)?.label ?? null });
|
|
238
|
+
}
|
|
239
|
+
return (_jsx(AriaLocale, { children: _jsxs(AriaSelect, { id: id, "aria-labelledby": labelledBy, "aria-label": nameOf(labelledBy, ariaLabel, placeholder), "aria-describedby": describedBy, value: value === "" ? null : value, onChange: (next) => onChange?.(next === null ? "" : String(next)), placeholder: placeholder, isInvalid: invalid, isDisabled: disabled, className: "w-full min-w-0", children: [_jsxs(AriaButton, { className: (state) => selectTrigger({ ...state, isInvalid: invalid }), children: [_jsx(SelectValue, { className: "min-w-0 flex-1 truncate data-placeholder:text-muted", children: ({ isPlaceholder, selectedText, defaultChildren }) => isPlaceholder ? defaultChildren : selectedText }), _jsx(ChevronDown, { "aria-hidden": true, className: "size-(--size-icon-md) shrink-0 text-muted", strokeWidth: 2 })] }), _jsx(Popover, { placement: "bottom start", className: "min-w-(--trigger-width)", children: _jsx(ListBox, { items: options, className: "max-h-[inherit] overflow-auto p-1 outline-0", children: (option) => (_jsx(ListBoxItem, { id: option.value, textValue: option.label, isDisabled: option.disabled, className: listOptionStyles, children: ({ isSelected }) => (_jsxs(_Fragment, { children: [_jsx("span", { className: `min-w-0 flex-1 truncate ${isSelected ? "font-medium" : ""}`, children: option.label }), _jsx("span", { className: "flex size-(--size-icon-md) shrink-0 items-center justify-center", children: isSelected ? (_jsx(Check, { "aria-hidden": true, className: "size-(--size-icon-md) text-primary forced-colors:text-[HighlightText]", strokeWidth: 2 })) : null })] })) })) }) })] }) }));
|
|
240
|
+
}
|