@colixsystems/widget-sdk 0.247.0 → 0.248.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/README.md +53 -5
- package/dist/board-auto-scroll.js +59 -0
- package/dist/board-view.js +327 -0
- package/dist/contract.cjs +70 -2
- package/dist/contract.js +70 -2
- package/dist/data-table-logic.js +179 -0
- package/dist/data-table-view.js +388 -0
- package/dist/data-table.js +22 -0
- package/dist/data-table.native.js +14 -0
- package/dist/drag-kit-view.js +310 -0
- package/dist/file-upload-view.js +13 -2
- package/dist/filter-bar-logic.js +143 -0
- package/dist/filter-bar-view.js +381 -0
- package/dist/filter-bar.js +15 -0
- package/dist/filter-bar.native.js +13 -0
- package/dist/form-control-logic.js +20 -3
- package/dist/form-control-view.js +18 -0
- package/dist/form-layout-logic.js +92 -0
- package/dist/form-view.js +268 -0
- package/dist/form.js +15 -0
- package/dist/form.native.js +9 -0
- package/dist/index.d.ts +372 -5
- package/dist/index.js +8 -0
- package/dist/index.native.js +8 -0
- package/dist/menu-view.js +5 -2
- package/dist/people-picker-view.js +3 -0
- package/dist/radio-group-view.js +7 -3
- package/dist/select-view.js +7 -2
- package/dist/sortable-list-view.js +228 -0
- package/dist/sortable-logic.js +273 -0
- package/dist/sortable-tokens.js +120 -0
- package/dist/sortable-view.js +24 -0
- package/dist/sortable.js +39 -0
- package/dist/sortable.native.js +30 -0
- package/dist/text-field-view.js +7 -2
- package/package.json +1 -1
- package/src/contract.js +70 -2
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// `<SortableList>` and `<Board>` (native). Bind ./sortable-view.js to React
|
|
2
|
+
// Native's own primitives; only the imports and the handle style differ from
|
|
3
|
+
// ./sortable.js.
|
|
4
|
+
|
|
5
|
+
import { View, Text, ScrollView, Animated, PanResponder, Platform, AccessibilityInfo } from "react-native";
|
|
6
|
+
import { List, ListRow } from "./list.native.js";
|
|
7
|
+
import { Card } from "./card.native.js";
|
|
8
|
+
import { Menu } from "./popover.native.js";
|
|
9
|
+
import { makeDragAndDrop } from "./sortable-view.js";
|
|
10
|
+
|
|
11
|
+
// A device scrolls nothing from a handle that claims the gesture.
|
|
12
|
+
const handleSurface = null;
|
|
13
|
+
// Nor can the page scroll under a held handle.
|
|
14
|
+
const onOutsideScroll = null;
|
|
15
|
+
|
|
16
|
+
export const { SortableList, Board } = makeDragAndDrop({
|
|
17
|
+
View,
|
|
18
|
+
Text,
|
|
19
|
+
ScrollView,
|
|
20
|
+
Animated,
|
|
21
|
+
PanResponder,
|
|
22
|
+
Platform,
|
|
23
|
+
AccessibilityInfo,
|
|
24
|
+
List,
|
|
25
|
+
ListRow,
|
|
26
|
+
Card,
|
|
27
|
+
Menu,
|
|
28
|
+
handleSurface,
|
|
29
|
+
onOutsideScroll,
|
|
30
|
+
});
|
package/dist/text-field-view.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// from, so the hosts cannot drift.
|
|
4
4
|
|
|
5
5
|
import React from "react";
|
|
6
|
-
import { useHostTheme } from "./hooks.js";
|
|
6
|
+
import { useHostSharedText, useHostTheme } from "./hooks.js";
|
|
7
7
|
import { DEFAULT_THEME_TOKENS } from "./_theme-tokens.js";
|
|
8
8
|
import { TEXT_FIELD_TOKEN_NAMES } from "./text-field-tokens.js";
|
|
9
9
|
import {
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
resolveFormControlTokens,
|
|
13
13
|
} from "./form-control-tokens.js";
|
|
14
14
|
import {
|
|
15
|
+
DRAWS_FIELD_FRAME,
|
|
15
16
|
errorMessage,
|
|
16
17
|
hasNote,
|
|
17
18
|
isShown,
|
|
@@ -44,11 +45,13 @@ export function makeTextField(rn) {
|
|
|
44
45
|
helper,
|
|
45
46
|
error,
|
|
46
47
|
disabled = false,
|
|
48
|
+
required = false,
|
|
47
49
|
containerStyle,
|
|
48
50
|
accessibilityLabel,
|
|
49
51
|
...others
|
|
50
52
|
} = props;
|
|
51
53
|
const theme = useHostTheme() || DEFAULT_THEME_TOKENS;
|
|
54
|
+
const requiredWord = useHostSharedText("required", "Required");
|
|
52
55
|
const [focused, setFocused] = React.useState(false);
|
|
53
56
|
const overrides = {};
|
|
54
57
|
const rest = {};
|
|
@@ -67,7 +70,7 @@ export function makeTextField(rn) {
|
|
|
67
70
|
ref: setRef,
|
|
68
71
|
multiline,
|
|
69
72
|
placeholderTextColor: placeholderTextColor || t.placeholderColor,
|
|
70
|
-
accessibilityLabel: spokenLabel(accessibilityLabel, label),
|
|
73
|
+
accessibilityLabel: spokenLabel(accessibilityLabel, label, required ? requiredWord : null),
|
|
71
74
|
...(disabled ? { editable: false, "aria-disabled": true } : null),
|
|
72
75
|
...(invalid ? { "aria-invalid": true } : null),
|
|
73
76
|
...(hasNote({ helper, error }) ? { "aria-describedby": noteId } : null),
|
|
@@ -113,6 +116,7 @@ export function makeTextField(rn) {
|
|
|
113
116
|
helper,
|
|
114
117
|
error: errorMessage(error),
|
|
115
118
|
disabled,
|
|
119
|
+
required,
|
|
116
120
|
noteId,
|
|
117
121
|
onLabelPress: () => {
|
|
118
122
|
const node = inputRef.current;
|
|
@@ -125,5 +129,6 @@ export function makeTextField(rn) {
|
|
|
125
129
|
});
|
|
126
130
|
|
|
127
131
|
TextField.displayName = "TextField";
|
|
132
|
+
TextField[DRAWS_FIELD_FRAME] = true;
|
|
128
133
|
return TextField;
|
|
129
134
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colixsystems/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.248.0",
|
|
4
4
|
"description": "Common widget interface for AppStudio. Implements WidgetManifest, WidgetContext, property schema, and helper hooks.",
|
|
5
5
|
"homepage": "https://github.com/Colix-AB/AppStudio",
|
|
6
6
|
"type": "module",
|
package/src/contract.js
CHANGED
|
@@ -3361,6 +3361,13 @@ const PRIMITIVES = [
|
|
|
3361
3361
|
rnComponent: null,
|
|
3362
3362
|
docsUrl: null,
|
|
3363
3363
|
},
|
|
3364
|
+
{
|
|
3365
|
+
name: "DataTable",
|
|
3366
|
+
description:
|
|
3367
|
+
'A list of records laid out for its own width. `<DataTable columns={[{ key: "name", label: "Name", flex: 2, sortable: true }, { key: "status", label: "Status", render: (row) => <Badge label={row.status} /> }, { key: "total", label: "Total", align: "right" }]} rows={data} loading={loading} onRowPress={(row) => open(row.id)} />` — use it by default for a list or table of records (datastore rows): it is stacked label/value cards when narrow, one card per row at medium (the first column as the title, the rest on one line), and aligned columns under a sticky header when wide, from its own measured width, so it needs no tier code of yours. When the table needs what it cannot do (editable cells, grouped or collapsible rows, resizable columns, a pivot), build your own from `<ListHeader>` over a `<List>` of `<ListRow>`s with the same `flex` per column. Columns: `key`, `label`, `flex` (default 1, the same in the header and the rows), `align` ("left" / "center" / "right"), `minWidth`, `sortable`, `render(row, column)` (a string or number becomes the cell\'s text). Props: `columns`, `rows`, `keyField` (default "id"), `renderCell(row, column)` (every cell without its own `render`; return `undefined` for the default), `onRowPress(row)`, `sort` / `defaultSort` (`{ key, direction: "asc" | "desc" }`), `onSortChange(sort)`, `maxHeight` (the rows\' scroll box, default 480), `loading` (placeholder rows while `rows` is empty), `emptyTitle`, `emptyMessage` (an empty state, "No results" by default), `sortLabels` (`{ sort, ascending, descending }` in the widget\'s language), the Lists overrides (`rowPadding`, `gap`, …), `style`, `testID`, `accessibilityLabel`. A sortable header is a 44px button named by its column and described by its sort, and a change of sort is announced once; the card layouts offer the same sort from a menu. With `onSortChange` you order the rows (re-query with the new sort) and the table draws `rows` as given; without it the table sorts them itself (a `Date` value sorts as a date). A controlled `sort` without `onSortChange` is display-only: the rows follow it and the headers are plain labels. The sticky header is filled with the rows\' Lists background, else the theme surface. It is styled by Design > Components > Lists (and Cards for the card layouts).',
|
|
3368
|
+
rnComponent: null,
|
|
3369
|
+
docsUrl: null,
|
|
3370
|
+
},
|
|
3364
3371
|
{
|
|
3365
3372
|
name: "Badge",
|
|
3366
3373
|
description:
|
|
@@ -3418,6 +3425,41 @@ const PRIMITIVES = [
|
|
|
3418
3425
|
rnComponent: null,
|
|
3419
3426
|
docsUrl: null,
|
|
3420
3427
|
},
|
|
3428
|
+
{
|
|
3429
|
+
name: "Form",
|
|
3430
|
+
description:
|
|
3431
|
+
'A form laid out in width tiers from its own measured width. `<Form><FieldRow><TextField label="First name" … /><TextField label="Last name" … /></FieldRow><TextField label="Email" required … /><FormActions><Button variant="secondary" title="Cancel" … /><Button title="Save" … /></FormActions></Form>` — use it by default for a widget that captures or edits data: its fields sit one to a line when narrow, two at medium and up to three when wide, and the form is capped near 1000px and centred, so it is never flush left, a narrow strip in an empty page, or a wall of giant inputs. A `<FormField>` or a control that frames its own label (`<TextField>`, `<Select>`, `<RadioGroup>`, `<PeoplePicker>`, `<FileUpload>`) is one field; a multiline field, a `<FormField span="full">`, a `<FieldRow>`, a `<FormSection>`, a `<FormActions>` and anything else (a `<Banner>`, a paragraph) take the whole line. Its direct children are read, so do not wrap them in a fragment. Props: `children`, `columns` ("auto" — three when wide — or 1–3, the most fields to a line), `maxWidth` (1000 by default, `null` for none, for a form in a side panel), `gap` (a theme spacing name, default "md", or a number), `style` (the outer box), `testID`, `accessibilityLabel`. When the form needs what it cannot do (a multi-step wizard, a form beside a live preview, fields laid over a picture), lay it out yourself in the app\'s look with `useContainerWidth()` and `widthTier()`, keeping the three tiers.',
|
|
3432
|
+
rnComponent: null,
|
|
3433
|
+
docsUrl: null,
|
|
3434
|
+
},
|
|
3435
|
+
{
|
|
3436
|
+
name: "FieldRow",
|
|
3437
|
+
description:
|
|
3438
|
+
'Short related fields on one line inside a `<Form>`: first + last name, city + postal code, expiry + CVC. `<FieldRow><TextField label="City" … /><TextField label="Postal code" … /></FieldRow>`. Up to three fields share the line; when narrow they stack. Props: `children`, `gap`, `style`, `testID`.',
|
|
3439
|
+
rnComponent: null,
|
|
3440
|
+
docsUrl: null,
|
|
3441
|
+
},
|
|
3442
|
+
{
|
|
3443
|
+
name: "FormSection",
|
|
3444
|
+
description:
|
|
3445
|
+
'A titled group of fields inside a `<Form>`, laid out in the form\'s own columns. `<FormSection title="Payment" description="Charged when the order ships">…</FormSection>`. Group a form past about six fields into sections. The title is a heading for screen readers too, in the subtitle text role; the description is in the helper colour. Props: `title`, `description`, `children`, `gap`, `style`, `testID`.',
|
|
3446
|
+
rnComponent: null,
|
|
3447
|
+
docsUrl: null,
|
|
3448
|
+
},
|
|
3449
|
+
{
|
|
3450
|
+
name: "FormActions",
|
|
3451
|
+
description:
|
|
3452
|
+
'A form\'s buttons in normal flow after its fields, never over them. `<FormActions><Button variant="secondary" title="Cancel" onPress={cancel} /><Button title="Save" onPress={save} busy={saving} /></FormActions>`. Pass the secondary first and the primary last: at medium and wide they sit in a row at the trailing edge, the primary last; when narrow they stack full-width in the same order, the primary nearest the thumb. Props: `children`, `style`, `testID`. In a `<Modal>`, pass the buttons to its `footer` instead.',
|
|
3453
|
+
rnComponent: null,
|
|
3454
|
+
docsUrl: null,
|
|
3455
|
+
},
|
|
3456
|
+
{
|
|
3457
|
+
name: "FormField",
|
|
3458
|
+
description:
|
|
3459
|
+
'The label, required mark, helper and error of the form controls, round any control. `<FormField label="Colour" required helper="Shown on the card" error={colourError}><SwatchPicker … /></FormField>`. A control that frames its own label (`<TextField>`, `<Select>`, `<RadioGroup>`, `<PeoplePicker>`, `<FileUpload>`) is handed them instead, so one label is drawn (a prop the control sets itself wins, `disabled={false}` included; the field\'s `testID` goes to the control, or to a wrapper round it when the control has its own); those five also take `required` directly. Any other child is framed here and named to a screen reader: on the web as a group, with the helper or error as its description; on a device the one child itself takes the label (with "Required") as its name and the helper or error as its hint, unless it names itself, when the label speaks the required word. Give that child no label of its own. `required` draws the mark and adds the app\'s "Required" to the spoken name; it does not validate, so pass `error` yourself. Props: `label`, `required`, `helper`, `error`, `disabled`, `span` ("full" takes the form\'s whole line), `style`, `testID`, `children`.',
|
|
3460
|
+
rnComponent: null,
|
|
3461
|
+
docsUrl: null,
|
|
3462
|
+
},
|
|
3421
3463
|
{
|
|
3422
3464
|
name: "Banner",
|
|
3423
3465
|
description:
|
|
@@ -3516,6 +3558,13 @@ const PRIMITIVES = [
|
|
|
3516
3558
|
rnComponent: null,
|
|
3517
3559
|
docsUrl: null,
|
|
3518
3560
|
},
|
|
3561
|
+
{
|
|
3562
|
+
name: "FilterBar",
|
|
3563
|
+
description:
|
|
3564
|
+
'Search plus facet filters that lay themselves out for their own width. `<FilterBar query={query} onQueryChange={setQuery} facets={[{ key: "status", label: t("status", "Status"), options: [{ value: "open", label: t("open", "Open") }] }]} value={filters} onChange={setFilters} sort={{ options: sortOptions, value: sortBy, onChange: setSortBy }} />` — use it by default for a list\'s search and filters; when the filters need what it cannot do (a date-range facet, a price slider, a map-area filter, saved filter presets), build your own toolbar from `<SearchField>`, `<Select>`, `<Chip>` and `<Modal>` in the same shape. It measures its own unpadded box: medium and wide are ONE toolbar row (the search field taking the free width, one compact `<Select>` per facet whose placeholder reads "Status: Any", then the sort) with each applied facet beneath as a removable chip ("Status: Open") and a "Clear all" that resets the facets and the query; narrow, or a row whose dropdowns would not fit, is the search field plus ONE "Filters · 2" button opening the facets as full-width Selects in a `<Modal>` sheet, applied on Apply (Clear empties the sheet\'s facets and puts its sort back on the first option). The applied chips and "Clear all" show beneath at every width, so a narrow user can drop one filter without the sheet. A facet is `{ key, label, options, multiple?, searchable? }`; `value` is `{ [key]: value }`, a list for a `multiple` facet, and `onChange(next)` fires once per edit with a cleared facet\'s key removed. `onQueryChange(query)` gets the trimmed query once typing pauses (`queryDebounceMs`, default 250) and at once on clear or submit; the search field shows only with it. Both are controlled, so seed `query` and `value` from route params or `useWidgetRoute`. Its parts keep their own looks (Design > Components > Search fields, Inputs, Dropdown menus, Chips, Dialogs); it adds no theme scope. Translate the copy with `t()`: `searchPlaceholder`, `anyText` ("Any"), `filtersText` ("Filters"), `activeCountText` ("{count} active"), `clearAllText`, `applyText`, `clearText`, `activeFiltersText`, `removeText` ("Remove {label}"). Other props: `style` (the outer box), `testID`.',
|
|
3565
|
+
rnComponent: null,
|
|
3566
|
+
docsUrl: null,
|
|
3567
|
+
},
|
|
3519
3568
|
{
|
|
3520
3569
|
name: "RadioGroup",
|
|
3521
3570
|
description:
|
|
@@ -3548,7 +3597,7 @@ const PRIMITIVES = [
|
|
|
3548
3597
|
{
|
|
3549
3598
|
name: "FileUpload",
|
|
3550
3599
|
description:
|
|
3551
|
-
'Themed file chooser. `<FileUpload label="Attachments" accept="image/*,.pdf" multiple files={files} onFilesChange={setFiles} />` — the default way to let the user pick files to upload, whether they upload at once or with a form. It is built on `<FilePicker>`: a button (Design > Components > Buttons) inside the app\'s input field (Design > Components > Inputs), the chosen files listed under it with their name, size and a remove control, and the same `label` / `helper` / `error` / `disabled` as every form control. The button opens the system file picker on both hosts and is the only way in on a device; on the web a file can also be dropped onto the field. A picked or dropped file that `accept` rules out is not added and the field shows an error. It uploads nothing itself: call `upload(file)` from `useFilestoreUpload` in `onPick` (or when the form is submitted) and pass `uploading` while it runs; the field then shows a `<Spinner>` with "Uploading…" and locks (no percentage is reported). Props: `accept` (an HTML accept value: "image/*", ".pdf", "application/pdf"), `multiple` (default false: a new pick replaces the file), `files` + `onFilesChange(files)` to own the list (omit both and it keeps its own; pass `files={[]}` to list nothing), `onPick(file)` (an array when `multiple`) once per pick or drop with only the new files, `onRemove(file, index)`, `onReject(files, reason)` (reason "type" for a file `accept` rules out, "count" for the files beyond the first when not `multiple`), `uploading`, `label`, `helper`, `error` (a message, or `true`), `disabled`, `buttonLabel` (default "Choose file" / "Choose files"), `buttonVariant` ("primary" | "secondary" | "outlined", the default), `uploadingLabel`, `emptyLabel`, `dropLabel`, `rejectedMessage`, `singleFileMessage`, `unsupportedMessage`, `removeAccessibilityLabel`, `accessibilityLabel`, `style` (the field), `containerStyle`, `testID` (also tags `<testID>-button`, `<testID>-file-<i>` and `<testID>-remove-<i>`). Pass translated copy for every label. Use it rather than a `<FilePicker>` round your own button, file list or "Uploading…" text; when the request needs what it cannot do (a grid of photo thumbnails, a cover image replaced on tap), wrap `<FilePicker>` round your own trigger in the app\'s look and still upload with `useFilestoreUpload`.',
|
|
3600
|
+
'Themed file chooser. `<FileUpload label="Attachments" accept="image/*,.pdf" multiple files={files} onFilesChange={setFiles} />` — the default way to let the user pick files to upload, whether they upload at once or with a form. It is built on `<FilePicker>`: a button (Design > Components > Buttons) inside the app\'s input field (Design > Components > Inputs), the chosen files listed under it with their name, size and a remove control, and the same `label` / `required` / `helper` / `error` / `disabled` as every form control. The button opens the system file picker on both hosts and is the only way in on a device; on the web a file can also be dropped onto the field. A picked or dropped file that `accept` rules out is not added and the field shows an error. It uploads nothing itself: call `upload(file)` from `useFilestoreUpload` in `onPick` (or when the form is submitted) and pass `uploading` while it runs; the field then shows a `<Spinner>` with "Uploading…" and locks (no percentage is reported). Props: `accept` (an HTML accept value: "image/*", ".pdf", "application/pdf"), `multiple` (default false: a new pick replaces the file), `files` + `onFilesChange(files)` to own the list (omit both and it keeps its own; pass `files={[]}` to list nothing), `onPick(file)` (an array when `multiple`) once per pick or drop with only the new files, `onRemove(file, index)`, `onReject(files, reason)` (reason "type" for a file `accept` rules out, "count" for the files beyond the first when not `multiple`), `uploading`, `label`, `required` (a mark after the label and "Required" in the picker\'s spoken name; it does not validate), `helper`, `error` (a message, or `true`), `disabled`, `buttonLabel` (default "Choose file" / "Choose files"), `buttonVariant` ("primary" | "secondary" | "outlined", the default), `uploadingLabel`, `emptyLabel`, `dropLabel`, `rejectedMessage`, `singleFileMessage`, `unsupportedMessage`, `removeAccessibilityLabel`, `accessibilityLabel`, `style` (the field), `containerStyle`, `testID` (also tags `<testID>-button`, `<testID>-file-<i>` and `<testID>-remove-<i>`). Pass translated copy for every label. Use it rather than a `<FilePicker>` round your own button, file list or "Uploading…" text; when the request needs what it cannot do (a grid of photo thumbnails, a cover image replaced on tap), wrap `<FilePicker>` round your own trigger in the app\'s look and still upload with `useFilestoreUpload`.',
|
|
3552
3601
|
rnComponent: null,
|
|
3553
3602
|
docsUrl: null,
|
|
3554
3603
|
},
|
|
@@ -3574,6 +3623,21 @@ const PRIMITIVES = [
|
|
|
3574
3623
|
rnComponent: null,
|
|
3575
3624
|
docsUrl: null,
|
|
3576
3625
|
},
|
|
3626
|
+
// Drag-and-drop: rows reordered and cards moved between columns.
|
|
3627
|
+
{
|
|
3628
|
+
name: "SortableList",
|
|
3629
|
+
description:
|
|
3630
|
+
'A list the user reorders by dragging. `<SortableList items={steps} onReorder={(next) => setSteps(next)} renderItem={({ item }) => <Text>{item.title}</Text>} />` — use it by default for a list the user reorders (a ranking, a playlist, the steps of a routine, priorities); when the request needs what it cannot do (free-form positioning on a canvas, a drag between two widgets, a drop onto a calendar slot, dragging several rows at once), build your own with `Animated` + `PanResponder` from react-native. Each row is a `<ListRow>` (the theme\'s `list` scope; `variant="card"` draws card rows) led by a grip handle that owns the drag on both hosts: the row lifts and follows the finger, the other rows step aside, the gap where it will land shows in the soft primary, and a release settles it there, or springs it back and calls nothing when nothing changed. A tap or Enter on the handle opens its move options (Move up, Move down, Move to top, Move to bottom), so dragging is never the only way; on the web the arrow keys move a focused handle, a screen reader is offered the same moves as actions, and every move is announced. `onReorder(nextItems, { from, to, item })` runs once per real change: show `nextItems` at once and save the new order yourself. Props: `items`, `onReorder`, `renderItem({ item, index, dragging })` (a row\'s content; with none the item\'s title shows), `keyExtractor` (default `item.id`), `getItemLabel` (the name a move announces; default its title, name or label), `canDrag` (a boolean or `(item) => boolean`; a row it refuses has no handle; it is read on every render, so pass `useCanWrite`\'s flag as it is), `variant` "plain" (default) | "card", `labels` (translated words: `handle` "Move {item}", `moveUp`, `moveDown`, `moveToTop`, `moveToBottom`, `moved` "{item} moved to position {position} of {total}", `hint`), `style`, `testID`, `accessibilityLabel`. In a list taller than the screen a drag reaches the rows in view; the move options reach any place.',
|
|
3631
|
+
rnComponent: null,
|
|
3632
|
+
docsUrl: null,
|
|
3633
|
+
},
|
|
3634
|
+
{
|
|
3635
|
+
name: "Board",
|
|
3636
|
+
description:
|
|
3637
|
+
'Cards the user moves between columns: a kanban, a pipeline, a status board. `<Board columns={[{ key: "todo", title: t("todo", "To do") }, { key: "done", title: t("done", "Done") }]} items={rows} getColumn={(row) => row[props.statusField]} onMove={(row, { toColumn }) => move(row, toColumn)} renderCard={({ item }) => <Text>{item[props.titleField]}</Text>} canDrag={canWrite} />` — use it by default for cards the user moves between columns; when the request needs what it cannot do (free-form positioning on a canvas, a drag between two widgets, a drop onto a calendar slot, swimlanes in two directions), build your own with `Animated` + `PanResponder` from react-native. Each card is a `<Card>` (the theme\'s `card` scope) with a grip handle. The board hit-tests the drop itself, highlights the one column the card is over in the soft primary with a line where it will land, and on a narrow box lays fixed-width columns in a sideways scroll that scrolls by itself while a card is held at its edge. A tap or Enter on the handle opens its move options (Move up, Move down, Move to <column>), so dragging is never the only way; on the web the arrow keys move a focused card (left and right to the end of the next column), a screen reader is offered the same moves as actions, and every move is announced. `onMove(item, { fromColumn, toColumn, index })` runs once per real change (`index` is its place in the target column); a drag released over no column, or where it started, springs back and calls nothing. The board draws `items` as you pass them, so show a moved card in its new column at once: keep an optimistic map until your write lands and you refetch, and on failure drop it with a toast so the card returns. Props: `columns` (`[{ key, title }]`), `items`, `getColumn(item)` (its column key), `onMove`, `renderCard({ item, index, column, dragging })` (a card\'s content), `keyExtractor`, `getItemLabel`, `canDrag` (a boolean or per item, read on every render), `reorderInColumn` (default true; false when cards keep no order of their own, and a drop in its own column then changes nothing), `columnWidth` (the narrow width of a column, default 280), `labels` (as SortableList, plus `moveToColumn` "Move to {column}" and `movedToColumn` "{item} moved to {column}, position {position}"), `style`, `testID`, `accessibilityLabel`.',
|
|
3638
|
+
rnComponent: null,
|
|
3639
|
+
docsUrl: null,
|
|
3640
|
+
},
|
|
3577
3641
|
];
|
|
3578
3642
|
|
|
3579
3643
|
const CATEGORIES = [
|
|
@@ -6254,7 +6318,11 @@ const CONTRACT = deepFreeze({
|
|
|
6254
6318
|
// 1.201.0: additive (sc-12070, sc-12071, sc-12072) — new `<Grid>`,
|
|
6255
6319
|
// `<Chart>` + `chart` theme scope + `chartColors`, `<PeoplePicker>`,
|
|
6256
6320
|
// searchable `<Select>`; `useGroups` takes `ids`.
|
|
6257
|
-
|
|
6321
|
+
// 1.202.0: additive (sc-12073, sc-12074, sc-12075, sc-12076) — new
|
|
6322
|
+
// `<DataTable>`, `<FilterBar>`, `<Form>` / `<FieldRow>` / `<FormSection>`
|
|
6323
|
+
// / `<FormActions>` / `<FormField>`, `<SortableList>`, `<Board>`;
|
|
6324
|
+
// `required` on TextField, Select, RadioGroup, PeoplePicker, FileUpload.
|
|
6325
|
+
version: "1.202.0",
|
|
6258
6326
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
6259
6327
|
hooks: HOOKS,
|
|
6260
6328
|
primitives: PRIMITIVES,
|