@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,52 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { AriaLocale } from "../../foundations/aria.js";
|
|
4
|
+
import { labelOf } from "./options.js";
|
|
5
|
+
import { FieldButton, FieldGroup, listOptionStyles, } from "../field/aria.js";
|
|
6
|
+
import { useFieldIds } from "../field/context.js";
|
|
7
|
+
import { ReadValue } from "../field/Field.js";
|
|
8
|
+
import { useFormMode } from "../field/mode.js";
|
|
9
|
+
import { Popover } from "../overlay/Popover.js";
|
|
10
|
+
import { Check, ChevronDown } from "lucide-react";
|
|
11
|
+
import { ComboBox as AriaComboBox, Input, ListBox, ListBoxItem, Text, useFilter, } from "react-aria-components";
|
|
12
|
+
/**
|
|
13
|
+
* One of many, found by typing: the customer of an invoice, the product of a line.
|
|
14
|
+
*
|
|
15
|
+
* Context: a `Select` makes you scroll a list you already know the name in; here you type
|
|
16
|
+
* three letters of it. The filter is React Aria's `contains` in the locale of the copy table
|
|
17
|
+
* (`AriaLocale`), so «jose» finds «José» and the comparison does not depend on the browser.
|
|
18
|
+
* The value is the option's `value` string — what the backend stores — never React Aria's
|
|
19
|
+
* `Key`.
|
|
20
|
+
*
|
|
21
|
+
* It is not `Picker`: that one chooses a RECORD among thousands, with metadata rows, a cap
|
|
22
|
+
* and a "see more". This is a closed list already in memory — tens to a few hundred — where
|
|
23
|
+
* one muted line under the name (a tax id, a city) is enough to tell two apart.
|
|
24
|
+
*
|
|
25
|
+
* When nothing matches, the list simply closes: an empty floating box tells nothing the
|
|
26
|
+
* unchanged field does not, and the system has no "no results" sentence of its own here.
|
|
27
|
+
*/
|
|
28
|
+
/* The row every list dropped from a field shares — Select, Suggest and this one. */
|
|
29
|
+
const option = listOptionStyles;
|
|
30
|
+
export function ComboBox({ mode, ...props }) {
|
|
31
|
+
/* Read mode draws the LABEL: the value is the backend's key, the label is what the form
|
|
32
|
+
promised to show. */
|
|
33
|
+
if (useFormMode(mode) === "read") {
|
|
34
|
+
return _jsx(ReadValue, { value: labelOf(props.options, props.value) });
|
|
35
|
+
}
|
|
36
|
+
/* The filter reads the locale from React Aria's context, so the box that provides it has
|
|
37
|
+
to be ABOVE the component that asks for it. */
|
|
38
|
+
return (_jsx(AriaLocale, { children: _jsx(EditableComboBox, { ...props }) }));
|
|
39
|
+
}
|
|
40
|
+
function EditableComboBox({ value, onChange, options, placeholder, disabled = false, id: ownId, describedBy: ownDescribedBy, invalid: ownInvalid, }) {
|
|
41
|
+
const { id, labelledBy, describedBy, invalid } = useFieldIds({
|
|
42
|
+
id: ownId,
|
|
43
|
+
describedBy: ownDescribedBy,
|
|
44
|
+
invalid: ownInvalid,
|
|
45
|
+
});
|
|
46
|
+
const { contains } = useFilter({ sensitivity: "base" });
|
|
47
|
+
return (_jsxs(AriaComboBox, { id: id, "aria-labelledby": labelledBy, "aria-describedby": describedBy,
|
|
48
|
+
/* `defaultItems` and not `items`: with `items` React Aria hands the filtering to the
|
|
49
|
+
consumer. These are the same options every render — only the filtering is React
|
|
50
|
+
Aria's. */
|
|
51
|
+
defaultItems: options, value: value, onChange: (next) => onChange?.(next === null ? null : String(next)), defaultFilter: contains, isInvalid: invalid, isDisabled: disabled, className: "w-full", children: [_jsxs(FieldGroup, { children: [_jsx(Input, { placeholder: placeholder, className: "h-full min-w-0 flex-1 bg-transparent px-2.5 text-meta text-ink outline-0 placeholder:text-muted disabled:cursor-not-allowed" }), _jsx(FieldButton, { className: "mr-1", children: _jsx(ChevronDown, { "aria-hidden": true, className: "size-(--size-icon-md)", strokeWidth: 2 }) })] }), _jsx(Popover, { placement: "bottom start", className: "w-(--trigger-width)", children: _jsx(ListBox, { items: options, className: "max-h-[inherit] overflow-auto p-1 outline-0", children: (item) => (_jsx(ListBoxItem, { id: item.value, textValue: item.label, isDisabled: item.disabled, className: option, children: ({ isSelected }) => (_jsxs(_Fragment, { children: [_jsxs("span", { className: "flex min-w-0 flex-1 flex-col", children: [_jsx(Text, { slot: "label", className: `truncate ${isSelected ? "font-medium" : ""}`, children: item.label }), item.description === undefined ? null : (_jsx(Text, { slot: "description", className: "truncate text-micro text-muted", children: item.description }))] }), _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 })] })) })) }) })] }));
|
|
52
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type ChoiceOption } from "./options.js";
|
|
2
|
+
import { type FormMode } from "../field/mode.js";
|
|
3
|
+
export type RadioGroupProps = {
|
|
4
|
+
/** The chosen option's `value`, or `null` while nothing is chosen. */
|
|
5
|
+
value: string | null;
|
|
6
|
+
onChange?: (next: string) => void;
|
|
7
|
+
options: readonly ChoiceOption[];
|
|
8
|
+
/** `horizontal` for a few one-word options in a wide form; descriptions read better in a
|
|
9
|
+
* column. */
|
|
10
|
+
orientation?: "vertical" | "horizontal";
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
id?: string;
|
|
13
|
+
describedBy?: string;
|
|
14
|
+
invalid?: boolean;
|
|
15
|
+
/** Forces the mode. By default it inherits the form's: in read mode it draws the label. */
|
|
16
|
+
mode?: FormMode;
|
|
17
|
+
};
|
|
18
|
+
export declare function RadioGroup({ value, onChange, options, orientation, disabled, id: ownId, describedBy: ownDescribedBy, invalid: ownInvalid, mode, }: RadioGroupProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { tv } from "../../foundations/variants.js";
|
|
4
|
+
import { labelOf } from "./options.js";
|
|
5
|
+
import { useFieldIds } from "../field/context.js";
|
|
6
|
+
import { ReadValue } from "../field/Field.js";
|
|
7
|
+
import { useFormMode } from "../field/mode.js";
|
|
8
|
+
import { RadioButton, RadioField, RadioGroup as AriaRadioGroup, Text, } from "react-aria-components";
|
|
9
|
+
/**
|
|
10
|
+
* One of few, all in sight: the payment method of a sale, the kind of a contact.
|
|
11
|
+
*
|
|
12
|
+
* Context: the rule against a `Select` — when there are few and there is room, they are
|
|
13
|
+
* shown, because knowing what can be chosen is half the decision. `SegmentedControl` draws
|
|
14
|
+
* them as buttons in a row, for two or three short words; this is the list form, for when an
|
|
15
|
+
* option needs a second line that explains it, or when there are five.
|
|
16
|
+
*
|
|
17
|
+
* The mark is the system's `Checkbox` mark made round — same size, same border, same
|
|
18
|
+
* primary when on — so a form that mixes both reads as one set of controls. A dot inside,
|
|
19
|
+
* not a check: a round mark with a check is a checkbox that lost its corners.
|
|
20
|
+
*/
|
|
21
|
+
const mark = tv({
|
|
22
|
+
base: "mt-0.5 flex size-4 shrink-0 items-center justify-center rounded-pill border transition-colors duration-(--duration-instant) forced-color-adjust-none",
|
|
23
|
+
variants: {
|
|
24
|
+
isSelected: {
|
|
25
|
+
false: "border-line-strong bg-surface forced-colors:border-[ButtonBorder]",
|
|
26
|
+
true: "border-primary bg-primary forced-colors:border-[Highlight] forced-colors:bg-[Highlight]",
|
|
27
|
+
},
|
|
28
|
+
isInvalid: {
|
|
29
|
+
true: "border-danger forced-colors:border-[Mark]",
|
|
30
|
+
},
|
|
31
|
+
isFocusVisible: {
|
|
32
|
+
true: "outline-2 outline-offset-2 outline-primary forced-colors:outline-[Highlight]",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
export function RadioGroup({ value, onChange, options, orientation = "vertical", disabled = false, id: ownId, describedBy: ownDescribedBy, invalid: ownInvalid, mode, }) {
|
|
37
|
+
const { id, labelledBy, describedBy, invalid } = useFieldIds({
|
|
38
|
+
id: ownId,
|
|
39
|
+
describedBy: ownDescribedBy,
|
|
40
|
+
invalid: ownInvalid,
|
|
41
|
+
});
|
|
42
|
+
if (useFormMode(mode) === "read")
|
|
43
|
+
return _jsx(ReadValue, { value: labelOf(options, value) });
|
|
44
|
+
return (_jsx(AriaRadioGroup, { id: id, "aria-labelledby": labelledBy, "aria-describedby": describedBy, value: value, onChange: (next) => onChange?.(next), orientation: orientation, isInvalid: invalid, isDisabled: disabled,
|
|
45
|
+
/* `py-2` puts the first label on the same line a text input's would sit on, so the
|
|
46
|
+
field does not look shorter than its neighbours in the same row. */
|
|
47
|
+
className: orientation === "horizontal"
|
|
48
|
+
? "flex min-w-0 flex-wrap items-start gap-x-5 gap-y-2 py-2"
|
|
49
|
+
: "flex min-w-0 flex-col gap-2.5 py-2", children: options.map((o) => (_jsxs(RadioField, { value: o.value, isDisabled: o.disabled, className: "flex min-w-0 flex-col data-disabled:opacity-45", children: [_jsx(RadioButton, { className: "group flex min-w-0 cursor-pointer items-start gap-2.5 text-meta text-ink disabled:cursor-not-allowed", children: (state) => (_jsxs(_Fragment, { children: [_jsx("span", { className: mark(state), children: _jsx("span", { className: `size-1.5 rounded-pill bg-surface transition-transform duration-(--duration-instant) ${state.isSelected ? "scale-100" : "scale-0"}` }) }), _jsx("span", { className: "min-w-0", children: o.label })] })) }), o.description === undefined ? null : (_jsx(Text, { slot: "description", className: "ml-6.5 text-micro text-muted", children: o.description }))] }, o.value))) }));
|
|
50
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type SearchFieldProps = {
|
|
2
|
+
value: string;
|
|
3
|
+
onChange?: (next: string) => void;
|
|
4
|
+
placeholder?: string;
|
|
5
|
+
/** Enter. For a search that runs on demand instead of on every keystroke. */
|
|
6
|
+
onSubmit?: (value: string) => void;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
id?: string;
|
|
9
|
+
describedBy?: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* A plain search box: a text, a clear button, Enter to submit. To filter the rows of a panel,
|
|
13
|
+
* the options of a long settings page, the lines of a document.
|
|
14
|
+
*
|
|
15
|
+
* Context: the system's `SearchBar` (in `components/search`) is a different thing — it builds
|
|
16
|
+
* CRITERIA: facets, operators, chips, a model of the fields. That is the list's question.
|
|
17
|
+
* This is the primitive under every other search: one string, no structure. Escape clears it
|
|
18
|
+
* and the clear button says so aloud in the copy's language — both React Aria's.
|
|
19
|
+
*
|
|
20
|
+
* It does not follow the form's read mode: what is typed here is never data of the record, so
|
|
21
|
+
* there is nothing to draw as text. The browser's own clear cross is hidden; ours is the one
|
|
22
|
+
* the keyboard and the screen reader know.
|
|
23
|
+
*/
|
|
24
|
+
export declare function SearchField({ value, onChange, placeholder, onSubmit, disabled, id: ownId, describedBy: ownDescribedBy, }: SearchFieldProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { AriaLocale } from "../../foundations/aria.js";
|
|
4
|
+
import { FieldButton, FieldGroup } from "../field/aria.js";
|
|
5
|
+
import { useFieldIds } from "../field/context.js";
|
|
6
|
+
import { Search, X } from "lucide-react";
|
|
7
|
+
import { Input, SearchField as AriaSearchField } from "react-aria-components";
|
|
8
|
+
/**
|
|
9
|
+
* A plain search box: a text, a clear button, Enter to submit. To filter the rows of a panel,
|
|
10
|
+
* the options of a long settings page, the lines of a document.
|
|
11
|
+
*
|
|
12
|
+
* Context: the system's `SearchBar` (in `components/search`) is a different thing — it builds
|
|
13
|
+
* CRITERIA: facets, operators, chips, a model of the fields. That is the list's question.
|
|
14
|
+
* This is the primitive under every other search: one string, no structure. Escape clears it
|
|
15
|
+
* and the clear button says so aloud in the copy's language — both React Aria's.
|
|
16
|
+
*
|
|
17
|
+
* It does not follow the form's read mode: what is typed here is never data of the record, so
|
|
18
|
+
* there is nothing to draw as text. The browser's own clear cross is hidden; ours is the one
|
|
19
|
+
* the keyboard and the screen reader know.
|
|
20
|
+
*/
|
|
21
|
+
export function SearchField({ value, onChange, placeholder, onSubmit, disabled = false, id: ownId, describedBy: ownDescribedBy, }) {
|
|
22
|
+
const { id, labelledBy, describedBy } = useFieldIds({
|
|
23
|
+
id: ownId,
|
|
24
|
+
describedBy: ownDescribedBy,
|
|
25
|
+
});
|
|
26
|
+
return (_jsx(AriaLocale, { children: _jsx(AriaSearchField, { id: id, "aria-labelledby": labelledBy, "aria-describedby": describedBy, value: value, onChange: (next) => onChange?.(next), onSubmit: onSubmit, isDisabled: disabled, className: "group w-full min-w-0", children: _jsxs(FieldGroup, { children: [_jsx(Search, { "aria-hidden": true, className: "ml-2.5 size-(--size-icon-md) shrink-0 text-muted forced-colors:text-[ButtonText]", strokeWidth: 2 }), _jsx(Input, { placeholder: placeholder, className: "h-full min-w-0 flex-1 bg-transparent px-2 text-meta text-ink outline-0 placeholder:text-muted disabled:cursor-not-allowed [&::-webkit-search-cancel-button]:hidden" }), _jsx(FieldButton, { className: "mr-1 group-data-empty:invisible", children: _jsx(X, { "aria-hidden": true, className: "size-(--size-icon-md)", strokeWidth: 2 }) })] }) }) }));
|
|
27
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type SegmentOption } from "./options.js";
|
|
2
|
+
import { type FormMode } from "../field/mode.js";
|
|
3
|
+
export type SegmentedControlProps = {
|
|
4
|
+
value: string;
|
|
5
|
+
onChange?: (next: string) => void;
|
|
6
|
+
options: readonly SegmentOption[];
|
|
7
|
+
/** `sm` in a toolbar or a list bar, next to the view switcher; `md` in a form row. */
|
|
8
|
+
size?: "sm" | "md";
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
describedBy?: string;
|
|
11
|
+
/** Forces the mode. By default it follows the form only when written inside a `Field`. */
|
|
12
|
+
mode?: FormMode;
|
|
13
|
+
/** The name of the group when it is not inside a `Field` — "Período". */
|
|
14
|
+
"aria-label"?: string;
|
|
15
|
+
};
|
|
16
|
+
export declare function SegmentedControl({ value, onChange, options, size, disabled, describedBy: ownDescribedBy, mode, "aria-label": ariaLabel, }: SegmentedControlProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { tv } from "../../foundations/variants.js";
|
|
4
|
+
import { labelOf } from "./options.js";
|
|
5
|
+
import { useFieldIds } from "../field/context.js";
|
|
6
|
+
import { ReadValue } from "../field/Field.js";
|
|
7
|
+
import { useFormMode } from "../field/mode.js";
|
|
8
|
+
import { ToggleButton, ToggleButtonGroup } from "react-aria-components";
|
|
9
|
+
/**
|
|
10
|
+
* Switching what is looked at: the period of a report ("Día · Semana · Mes"), the grouping of
|
|
11
|
+
* a board. One of a few, always one, and the change is immediate.
|
|
12
|
+
*
|
|
13
|
+
* Context: it draws exactly what the list's view switcher (`ViewSwitcher`) draws — one
|
|
14
|
+
* bordered box split by hairlines, the chosen segment tinted with ink, the others muted — so
|
|
15
|
+
* every "which view" control of a screen reads as the same kind of thing. It adds a label
|
|
16
|
+
* next to the icon, because a period has no icon that says "Semana".
|
|
17
|
+
*
|
|
18
|
+
* It is a VIEW control first, so it ignores the form's read mode — a report in a record that
|
|
19
|
+
* is being looked at still has to switch period. Written inside a `Field` it is a form answer
|
|
20
|
+
* instead (the kind of a contact: Persona · Empresa), and then it follows the form: in read
|
|
21
|
+
* mode it draws the chosen label.
|
|
22
|
+
*/
|
|
23
|
+
const group = tv({
|
|
24
|
+
base: "inline-flex w-fit max-w-full min-w-0 shrink-0 items-stretch overflow-hidden rounded-control border border-line-strong bg-surface forced-colors:border-[ButtonBorder]",
|
|
25
|
+
variants: {
|
|
26
|
+
size: {
|
|
27
|
+
sm: "h-(--size-control-sm)",
|
|
28
|
+
md: "h-(--size-control-md)",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
const segment = tv({
|
|
33
|
+
base: "inline-flex min-w-0 cursor-pointer items-center justify-center gap-1.5 border-l border-line-strong text-meta whitespace-nowrap transition-colors duration-(--duration-instant) outline-0 first:border-l-0 forced-color-adjust-none forced-colors:border-[ButtonBorder]",
|
|
34
|
+
variants: {
|
|
35
|
+
size: {
|
|
36
|
+
sm: "px-2.5",
|
|
37
|
+
md: "px-3.5",
|
|
38
|
+
},
|
|
39
|
+
isSelected: {
|
|
40
|
+
false: "bg-surface text-muted hover:bg-ink/5 hover:text-ink pressed:bg-ink/10",
|
|
41
|
+
true: "bg-ink/8 text-ink forced-colors:bg-[Highlight] forced-colors:text-[HighlightText]",
|
|
42
|
+
},
|
|
43
|
+
isFocusVisible: {
|
|
44
|
+
true: "outline-2 -outline-offset-2 outline-primary forced-colors:outline-[Highlight]",
|
|
45
|
+
},
|
|
46
|
+
isDisabled: {
|
|
47
|
+
true: "cursor-not-allowed opacity-45",
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
export function SegmentedControl({ value, onChange, options, size = "md", disabled = false, describedBy: ownDescribedBy, mode, "aria-label": ariaLabel, }) {
|
|
52
|
+
/* No `id`: a group of buttons is not something a `<label for>` can point at; the field
|
|
53
|
+
names it through `aria-labelledby`. */
|
|
54
|
+
const { labelledBy, describedBy } = useFieldIds({ describedBy: ownDescribedBy });
|
|
55
|
+
const formMode = useFormMode(mode);
|
|
56
|
+
const inForm = mode !== undefined || labelledBy !== undefined;
|
|
57
|
+
if (inForm && formMode === "read")
|
|
58
|
+
return _jsx(ReadValue, { value: labelOf(options, value) });
|
|
59
|
+
return (_jsx(ToggleButtonGroup, { "aria-label": ariaLabel, "aria-labelledby": labelledBy, "aria-describedby": describedBy, selectionMode: "single",
|
|
60
|
+
/* Always one: a period switcher with nothing pressed shows a report of no period. */
|
|
61
|
+
disallowEmptySelection: true, selectedKeys: [value], onSelectionChange: (keys) => {
|
|
62
|
+
const [next] = keys;
|
|
63
|
+
if (next !== undefined && String(next) !== value)
|
|
64
|
+
onChange?.(String(next));
|
|
65
|
+
}, isDisabled: disabled, className: group({ size }), children: options.map((o) => {
|
|
66
|
+
const Icon = o.icon;
|
|
67
|
+
return (_jsxs(ToggleButton, { id: o.value, isDisabled: o.disabled, className: (state) => segment({ ...state, size }), children: [Icon === undefined ? null : (_jsx(Icon, { "aria-hidden": true, className: "size-(--size-icon-md) shrink-0", strokeWidth: 2 })), o.label] }, o.value));
|
|
68
|
+
}) }));
|
|
69
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type FormMode } from "../field/mode.js";
|
|
2
|
+
export type SliderProps = {
|
|
3
|
+
value: number;
|
|
4
|
+
onChange?: (next: number) => void;
|
|
5
|
+
min: number;
|
|
6
|
+
max: number;
|
|
7
|
+
step?: number;
|
|
8
|
+
/** How the figure is written, in the copy's locale. `{ style: "percent" }` takes 0–1. */
|
|
9
|
+
format?: Intl.NumberFormatOptions;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
id?: string;
|
|
12
|
+
describedBy?: string;
|
|
13
|
+
/** Forces the mode. By default it inherits the form's: in read mode it draws the figure. */
|
|
14
|
+
mode?: FormMode;
|
|
15
|
+
};
|
|
16
|
+
export declare function Slider({ value, onChange, min, max, step, format, disabled, id: ownId, describedBy: ownDescribedBy, mode, }: SliderProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { AriaLocale } from "../../foundations/aria.js";
|
|
4
|
+
import { useUiCopy } from "../../foundations/copy.js";
|
|
5
|
+
import { tv } from "../../foundations/variants.js";
|
|
6
|
+
import { useFieldIds } from "../field/context.js";
|
|
7
|
+
import { ReadValue } from "../field/Field.js";
|
|
8
|
+
import { useFormMode } from "../field/mode.js";
|
|
9
|
+
import { Slider as AriaSlider, SliderFill, SliderOutput, SliderThumb, SliderTrack, } from "react-aria-components";
|
|
10
|
+
/**
|
|
11
|
+
* A number in a range, where the position says as much as the figure: a discount, a
|
|
12
|
+
* tolerance, a probability.
|
|
13
|
+
*
|
|
14
|
+
* Context: the figure is always written next to the track — a slider alone answers "about
|
|
15
|
+
* there", and an invoice needs "12 %". It is formatted by React Aria in the locale of the copy
|
|
16
|
+
* table (`AriaLocale`), with the consumer's `Intl.NumberFormatOptions`: `{ style: "percent" }`
|
|
17
|
+
* expects a fraction (0.12), a plain number with a unit is the consumer's `unit` style.
|
|
18
|
+
*
|
|
19
|
+
* When the exact figure matters more than the position — an amount, a quantity — the control
|
|
20
|
+
* is `NumberInput`: dragging to 1 250,00 is a game nobody wins.
|
|
21
|
+
*/
|
|
22
|
+
const thumb = tv({
|
|
23
|
+
base: "top-1/2 size-4 rounded-pill border-2 border-primary bg-surface shadow-raised transition-colors duration-(--duration-instant) forced-color-adjust-none forced-colors:border-[Highlight]",
|
|
24
|
+
variants: {
|
|
25
|
+
isDragging: {
|
|
26
|
+
true: "bg-primary",
|
|
27
|
+
},
|
|
28
|
+
isFocusVisible: {
|
|
29
|
+
true: "outline-2 outline-offset-2 outline-primary forced-colors:outline-[Highlight]",
|
|
30
|
+
},
|
|
31
|
+
isDisabled: {
|
|
32
|
+
true: "border-line-strong forced-colors:border-[GrayText]",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
export function Slider({ value, onChange, min, max, step = 1, format, disabled = false, id: ownId, describedBy: ownDescribedBy, mode, }) {
|
|
37
|
+
const copy = useUiCopy();
|
|
38
|
+
const { id, labelledBy, describedBy } = useFieldIds({
|
|
39
|
+
id: ownId,
|
|
40
|
+
describedBy: ownDescribedBy,
|
|
41
|
+
});
|
|
42
|
+
if (useFormMode(mode) === "read") {
|
|
43
|
+
return _jsx(ReadValue, { value: new Intl.NumberFormat(copy.locale, format).format(value) });
|
|
44
|
+
}
|
|
45
|
+
return (_jsx(AriaLocale, { children: _jsxs(AriaSlider, { id: id, "aria-labelledby": labelledBy, "aria-describedby": describedBy, value: value, onChange: (next) => onChange?.(next), minValue: min, maxValue: max, step: step, formatOptions: format, isDisabled: disabled, className: "flex h-(--size-control-md) w-full min-w-0 items-center gap-3 disabled:opacity-45", children: [_jsxs(SliderTrack, { className: "relative flex h-(--size-control-md) min-w-0 flex-1 cursor-pointer items-center disabled:cursor-not-allowed", children: [_jsx("span", { className: "relative h-1 w-full rounded-pill bg-line-strong forced-colors:bg-[ButtonBorder]", children: _jsx(SliderFill, { className: "rounded-pill bg-primary disabled:bg-muted forced-colors:bg-[Highlight]" }) }), _jsx(SliderThumb, { "aria-labelledby": labelledBy, "aria-describedby": describedBy, className: thumb })] }), _jsx(SliderOutput, { className: "w-14 shrink-0 text-right text-meta text-ink tabular-nums" })] }) }));
|
|
46
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type FormMode } from "../field/mode.js";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
export type SwitchProps = {
|
|
4
|
+
checked: boolean;
|
|
5
|
+
onChange?: (next: boolean) => void;
|
|
6
|
+
/** The sentence next to the track — part of the click target. */
|
|
7
|
+
children?: ReactNode;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
id?: string;
|
|
10
|
+
describedBy?: string;
|
|
11
|
+
/** Forces the mode. By default it inherits the form's: in read mode it draws the answer. */
|
|
12
|
+
mode?: FormMode;
|
|
13
|
+
};
|
|
14
|
+
export declare function Switch({ checked, onChange, children, disabled, id: ownId, describedBy: ownDescribedBy, mode, }: SwitchProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { tv } from "../../foundations/variants.js";
|
|
4
|
+
import { useFieldIds } from "../field/context.js";
|
|
5
|
+
import { useFormMode } from "../field/mode.js";
|
|
6
|
+
import { Check, Minus } from "lucide-react";
|
|
7
|
+
import { useId } from "react";
|
|
8
|
+
import { Switch as AriaSwitch } from "react-aria-components";
|
|
9
|
+
/**
|
|
10
|
+
* On or off, and it takes effect NOW: "send the invoice by email", "show archived".
|
|
11
|
+
*
|
|
12
|
+
* Context: it is React Aria's — a real input underneath, with the press, hover and keyboard
|
|
13
|
+
* focus of the rest of this family. Its inline label is optional and can be any node: inside
|
|
14
|
+
* a `Field` the field's label already names it, and the children add the sentence that says
|
|
15
|
+
* what "on" means (both are read aloud). It replaced the hand-written `Toggle`: one switch,
|
|
16
|
+
* one component.
|
|
17
|
+
*
|
|
18
|
+
* It is not a checkbox. A check is an answer that waits for the form to be submitted; a
|
|
19
|
+
* switch promises the change already happened. The track is `primary` when on — the
|
|
20
|
+
* system's color for a chosen state — never the gold of the one main action.
|
|
21
|
+
*/
|
|
22
|
+
const track = tv({
|
|
23
|
+
base: "relative inline-flex h-5 w-9 shrink-0 items-center rounded-pill transition-colors duration-(--duration-quick) forced-color-adjust-none",
|
|
24
|
+
variants: {
|
|
25
|
+
isSelected: {
|
|
26
|
+
false: "bg-line-strong forced-colors:bg-[ButtonBorder]",
|
|
27
|
+
true: "bg-primary forced-colors:bg-[Highlight]",
|
|
28
|
+
},
|
|
29
|
+
isPressed: {
|
|
30
|
+
true: "brightness-95",
|
|
31
|
+
},
|
|
32
|
+
isFocusVisible: {
|
|
33
|
+
true: "outline-2 outline-offset-2 outline-primary forced-colors:outline-[Highlight]",
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
const thumb = tv({
|
|
38
|
+
base: "inline-block size-4 rounded-pill bg-surface shadow-raised transition-transform duration-(--duration-quick) ease-(--ease-out) forced-colors:bg-[ButtonText]",
|
|
39
|
+
variants: {
|
|
40
|
+
isSelected: {
|
|
41
|
+
false: "translate-x-0.5",
|
|
42
|
+
true: "translate-x-4.5",
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
export function Switch({ checked, onChange, children, disabled = false, id: ownId, describedBy: ownDescribedBy, mode, }) {
|
|
47
|
+
const { id, labelledBy, describedBy } = useFieldIds({
|
|
48
|
+
id: ownId,
|
|
49
|
+
describedBy: ownDescribedBy,
|
|
50
|
+
});
|
|
51
|
+
const textId = useId();
|
|
52
|
+
/* Named by the field AND by its own sentence: «Notificaciones, enviar la factura por
|
|
53
|
+
correo» — either one alone loses half of what the switch does. */
|
|
54
|
+
const named = [labelledBy, children === undefined ? undefined : textId]
|
|
55
|
+
.filter((part) => part !== undefined)
|
|
56
|
+
.join(" ") || undefined;
|
|
57
|
+
/* In read mode an off track looks like a disabled one. The answer is drawn as a mark. */
|
|
58
|
+
if (useFormMode(mode) === "read") {
|
|
59
|
+
return (_jsxs("span", { className: "flex min-h-(--size-control-md) items-center gap-1.5 text-meta text-ink", children: [checked ? (_jsx(Check, { "aria-hidden": true, className: "size-(--size-icon-sm) text-good", strokeWidth: 2.5 })) : (_jsx(Minus, { "aria-hidden": true, className: "size-(--size-icon-sm) text-muted", strokeWidth: 2.5 })), children] }));
|
|
60
|
+
}
|
|
61
|
+
return (_jsx(AriaSwitch, { id: id, "aria-labelledby": named, "aria-describedby": describedBy, isSelected: checked, onChange: (next) => onChange?.(next), isDisabled: disabled,
|
|
62
|
+
/* The height of a control, so a switch alone in a field lines up with a text input in
|
|
63
|
+
the next column. */
|
|
64
|
+
className: "group inline-flex min-h-(--size-control-md) min-w-0 cursor-pointer items-center gap-2.5 text-meta text-ink disabled:cursor-not-allowed disabled:opacity-45", children: (state) => (_jsxs(_Fragment, { children: [_jsx("span", { className: track(state), children: _jsx("span", { className: thumb(state) }) }), children === undefined ? null : (_jsx("span", { id: textId, className: "min-w-0", children: children }))] })) }));
|
|
65
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context: the app's way to let a person pick the palette — a `SegmentedControl` over
|
|
3
|
+
* `useTheme()` and nothing more. System / Light / Dark are always all three: "system" is the
|
|
4
|
+
* default and the only way back from a forced choice, so it is never left out. It writes through
|
|
5
|
+
* the nearest `ThemeProvider` (or straight to `<html>` without one); where the choice is
|
|
6
|
+
* remembered is the app's, via the provider's `onChange`.
|
|
7
|
+
*/
|
|
8
|
+
export declare function ThemeSwitcher({ size }: {
|
|
9
|
+
size?: "sm" | "md";
|
|
10
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useUiCopy } from "../../foundations/copy.js";
|
|
4
|
+
import { isTheme, useTheme } from "../../foundations/theme.js";
|
|
5
|
+
import { SegmentedControl } from "./SegmentedControl.js";
|
|
6
|
+
import { Monitor, Moon, Sun } from "lucide-react";
|
|
7
|
+
/**
|
|
8
|
+
* Context: the app's way to let a person pick the palette — a `SegmentedControl` over
|
|
9
|
+
* `useTheme()` and nothing more. System / Light / Dark are always all three: "system" is the
|
|
10
|
+
* default and the only way back from a forced choice, so it is never left out. It writes through
|
|
11
|
+
* the nearest `ThemeProvider` (or straight to `<html>` without one); where the choice is
|
|
12
|
+
* remembered is the app's, via the provider's `onChange`.
|
|
13
|
+
*/
|
|
14
|
+
export function ThemeSwitcher({ size = "sm" }) {
|
|
15
|
+
const copy = useUiCopy().theme;
|
|
16
|
+
const { theme, setTheme } = useTheme();
|
|
17
|
+
return (_jsx(SegmentedControl, { "aria-label": copy.label, size: size, value: theme, onChange: (next) => {
|
|
18
|
+
if (isTheme(next))
|
|
19
|
+
setTheme(next);
|
|
20
|
+
}, options: [
|
|
21
|
+
{ value: "system", label: copy.system, icon: Monitor },
|
|
22
|
+
{ value: "light", label: copy.light, icon: Sun },
|
|
23
|
+
{ value: "dark", label: copy.dark, icon: Moon },
|
|
24
|
+
] }));
|
|
25
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { ComboBox, type ComboBoxProps } from "./ComboBox.js";
|
|
2
|
+
export { type ChoiceOption, type SegmentOption, } from "./options.js";
|
|
3
|
+
export { RadioGroup, type RadioGroupProps, } from "./RadioGroup.js";
|
|
4
|
+
export { SearchField, type SearchFieldProps, } from "./SearchField.js";
|
|
5
|
+
export { SegmentedControl, type SegmentedControlProps, } from "./SegmentedControl.js";
|
|
6
|
+
export { Slider, type SliderProps } from "./Slider.js";
|
|
7
|
+
export { Switch, type SwitchProps } from "./Switch.js";
|
|
8
|
+
export { ThemeSwitcher } from "./ThemeSwitcher.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/* LAS ELECCIONES — elegir uno de muchos escribiendo (ComboBox), uno de pocos a la vista
|
|
2
|
+
(RadioGroup, SegmentedControl), prender o apagar (Switch), un valor en un rango (Slider)
|
|
3
|
+
y buscar un texto suelto (SearchField). El valor es siempre un dato plano. */
|
|
4
|
+
export { ComboBox } from "./ComboBox.js";
|
|
5
|
+
export {} from "./options.js";
|
|
6
|
+
export { RadioGroup, } from "./RadioGroup.js";
|
|
7
|
+
export { SearchField, } from "./SearchField.js";
|
|
8
|
+
export { SegmentedControl, } from "./SegmentedControl.js";
|
|
9
|
+
export { Slider } from "./Slider.js";
|
|
10
|
+
export { Switch } from "./Switch.js";
|
|
11
|
+
export { ThemeSwitcher } from "./ThemeSwitcher.js";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { LucideIcon } from "lucide-react";
|
|
2
|
+
/**
|
|
3
|
+
* One answer of a choice: what is stored (`value`) and what is read (`label`).
|
|
4
|
+
*
|
|
5
|
+
* Context: a plain object and not React Aria's `Key` + children, because the options of an
|
|
6
|
+
* ERP form come from data — the currencies of the company, the payment methods configured —
|
|
7
|
+
* and the value goes back to the backend as the same string it came in with.
|
|
8
|
+
*/
|
|
9
|
+
export interface ChoiceOption {
|
|
10
|
+
value: string;
|
|
11
|
+
label: string;
|
|
12
|
+
/** A second, muted line: what the option IS (a tax id, a city), not a second name. */
|
|
13
|
+
description?: string;
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/** One segment of a `SegmentedControl`. The icon goes before the label, never instead of it. */
|
|
17
|
+
export interface SegmentOption {
|
|
18
|
+
value: string;
|
|
19
|
+
label: string;
|
|
20
|
+
icon?: LucideIcon;
|
|
21
|
+
/** Shown but not choosable — a period the report does not have yet. */
|
|
22
|
+
disabled?: boolean;
|
|
23
|
+
}
|
|
24
|
+
/** The label of the option whose value is `value`, for read mode. */
|
|
25
|
+
export declare function labelOf(options: readonly {
|
|
26
|
+
value: string;
|
|
27
|
+
label: string;
|
|
28
|
+
}[], value: string | null): string | null;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type CalendarProps = {
|
|
2
|
+
/** The chosen day, as an ISO date — `"2026-10-30"`. */
|
|
3
|
+
value?: string | null;
|
|
4
|
+
onChange?: (next: string) => void;
|
|
5
|
+
/** The earliest and latest day that can be chosen, as ISO dates. */
|
|
6
|
+
min?: string;
|
|
7
|
+
max?: string;
|
|
8
|
+
/** A day that cannot be chosen — a holiday, a closed day. Receives the ISO date. */
|
|
9
|
+
isUnavailable?: (date: string) => boolean;
|
|
10
|
+
"aria-label"?: string;
|
|
11
|
+
};
|
|
12
|
+
/** One month, to pick one day. */
|
|
13
|
+
export declare function Calendar({ value, onChange, min, max, isUnavailable, ...rest }: CalendarProps): import("react").JSX.Element;
|
|
14
|
+
export type RangeCalendarProps = {
|
|
15
|
+
/** The chosen span, as ISO dates. */
|
|
16
|
+
value?: {
|
|
17
|
+
start: string;
|
|
18
|
+
end: string;
|
|
19
|
+
} | null;
|
|
20
|
+
onChange?: (next: {
|
|
21
|
+
start: string;
|
|
22
|
+
end: string;
|
|
23
|
+
}) => void;
|
|
24
|
+
min?: string;
|
|
25
|
+
max?: string;
|
|
26
|
+
/** Two months side by side: what a range across a month boundary needs to be seen whole. */
|
|
27
|
+
months?: 1 | 2;
|
|
28
|
+
"aria-label"?: string;
|
|
29
|
+
};
|
|
30
|
+
/** One or two months, to pick a span: the first click opens it, the second closes it. */
|
|
31
|
+
export declare function RangeCalendar({ months, value, onChange, min, max, ...rest }: RangeCalendarProps): import("react").JSX.Element;
|