@colixsystems/widget-sdk 0.245.0 → 0.247.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 +60 -2
- package/dist/button-tokens.js +154 -0
- package/dist/button-view.js +251 -93
- package/dist/button.js +5 -3
- package/dist/button.native.js +6 -4
- package/dist/chart-logic.js +575 -0
- package/dist/chart-tokens.js +131 -0
- package/dist/chart-view.js +262 -0
- package/dist/chart.js +21 -0
- package/dist/chart.native.js +10 -0
- package/dist/contract.cjs +132 -35
- package/dist/contract.js +132 -35
- package/dist/detail-parts-view.js +31 -2
- package/dist/file-image-logic.js +162 -0
- package/dist/file-image-source.js +71 -0
- package/dist/file-image-view.js +119 -0
- package/dist/file-image.js +8 -0
- package/dist/file-image.native.js +8 -0
- package/dist/form-control-logic.js +23 -0
- package/dist/form-control-tokens.js +25 -10
- package/dist/grid-logic.js +222 -0
- package/dist/grid-view.js +109 -0
- package/dist/grid.js +7 -0
- package/dist/grid.native.js +7 -0
- package/dist/host.d.ts +6 -0
- package/dist/host.js +2 -0
- package/dist/index.d.ts +481 -20
- package/dist/index.js +14 -1
- package/dist/index.native.js +14 -1
- package/dist/menu-view.js +168 -0
- package/dist/overlay-tokens.js +50 -0
- package/dist/overlay-view.js +67 -24
- package/dist/overlay.native.js +3 -1
- package/dist/people-picker-logic.js +203 -0
- package/dist/people-picker-view.js +247 -0
- package/dist/people-picker.js +11 -0
- package/dist/people-picker.native.js +9 -0
- package/dist/popover-tokens.js +145 -0
- package/dist/popover-view.js +252 -0
- package/dist/popover.js +23 -0
- package/dist/popover.native.js +22 -0
- package/dist/radio-group-logic.js +61 -0
- package/dist/radio-group-tokens.js +62 -0
- package/dist/radio-group-view.js +262 -0
- package/dist/radio-group.js +12 -0
- package/dist/radio-group.native.js +7 -0
- package/dist/select-search-logic.js +150 -0
- package/dist/select-view.js +255 -43
- package/dist/select.js +6 -2
- package/dist/select.native.js +5 -3
- package/dist/tabs-tokens.js +59 -0
- package/dist/tabs-view.js +9 -43
- package/dist/tooltip-view.js +137 -0
- package/dist/web-modal.js +95 -23
- package/package.json +1 -1
- package/src/contract.js +132 -35
package/dist/contract.cjs
CHANGED
|
@@ -622,7 +622,7 @@ const THEME_COMPONENTS = Object.freeze({
|
|
|
622
622
|
muted: Object.freeze({
|
|
623
623
|
label: "Inputs",
|
|
624
624
|
// The SDK's form controls read this scope directly, in every widget.
|
|
625
|
-
sdkComponents: Object.freeze(["TextField", "Checkbox", "Switch", "Select", "SearchField", "FileUpload"]),
|
|
625
|
+
sdkComponents: Object.freeze(["TextField", "Checkbox", "Switch", "Select", "SearchField", "FileUpload", "RadioGroup", "PeoplePicker"]),
|
|
626
626
|
tokens: Object.freeze({
|
|
627
627
|
background: Object.freeze({ type: "color", uiDefault: "colors.surfaceMuted" }),
|
|
628
628
|
textColor: Object.freeze({ type: "color", uiDefault: "colors.onSurfaceMuted" }),
|
|
@@ -636,15 +636,18 @@ const THEME_COMPONENTS = Object.freeze({
|
|
|
636
636
|
"appstudio.form-builder": MUTED_INPUT_FIELDS,
|
|
637
637
|
}),
|
|
638
638
|
}),
|
|
639
|
-
// sc-5392 -- dropdown/menu surfaces
|
|
640
|
-
// list
|
|
639
|
+
// sc-5392 -- dropdown/menu surfaces: the form widgets' choice-field option
|
|
640
|
+
// list, and the SDK's option list, menus, popovers and tooltips. Unset
|
|
641
|
+
// radius and shadow keep the `<Overlay>` panel's own.
|
|
641
642
|
popover: Object.freeze({
|
|
642
643
|
label: "Dropdown menus",
|
|
643
|
-
sdkComponents: Object.freeze(["Select"]),
|
|
644
|
+
sdkComponents: Object.freeze(["Select", "Menu", "Popover", "Tooltip", "PeoplePicker"]),
|
|
644
645
|
tokens: Object.freeze({
|
|
645
646
|
background: Object.freeze({ type: "color", uiDefault: "colors.surface" }),
|
|
646
647
|
textColor: Object.freeze({ type: "color", uiDefault: "colors.onSurface" }),
|
|
647
648
|
borderColor: Object.freeze({ type: "color", uiDefault: "colors.border" }),
|
|
649
|
+
radius: Object.freeze({ type: "size", min: 0, max: 32, uiDefault: "radii.lg" }),
|
|
650
|
+
shadow: Object.freeze({ type: "shadow" }),
|
|
648
651
|
}),
|
|
649
652
|
targets: Object.freeze({
|
|
650
653
|
"appstudio.form-input": POPOVER_FIELDS,
|
|
@@ -701,6 +704,8 @@ const THEME_COMPONENTS = Object.freeze({
|
|
|
701
704
|
// the fields bind it onto widgets. `style` is the first `enum` token.
|
|
702
705
|
tabs: Object.freeze({
|
|
703
706
|
label: "Tabs",
|
|
707
|
+
// A segmented `<RadioGroup>` draws this scope's segmented strip.
|
|
708
|
+
sdkComponents: Object.freeze(["Tabs", "RadioGroup"]),
|
|
704
709
|
universalFields: TABS_FIELDS,
|
|
705
710
|
tokens: Object.freeze({
|
|
706
711
|
style: Object.freeze({ type: "enum", values: THEME_TAB_STYLES }),
|
|
@@ -779,6 +784,25 @@ const THEME_COMPONENTS = Object.freeze({
|
|
|
779
784
|
}),
|
|
780
785
|
targets: Object.freeze({}),
|
|
781
786
|
}),
|
|
787
|
+
// Charts: `<Chart>` reads this scope directly, so it names the SDK
|
|
788
|
+
// component it styles and binds no widget field. Unset series colours
|
|
789
|
+
// follow the theme's own colours, so their defaults are derived.
|
|
790
|
+
chart: Object.freeze({
|
|
791
|
+
label: "Charts",
|
|
792
|
+
sdkComponents: Object.freeze(["Chart"]),
|
|
793
|
+
tokens: Object.freeze({
|
|
794
|
+
series1: Object.freeze({ type: "color" }),
|
|
795
|
+
series2: Object.freeze({ type: "color" }),
|
|
796
|
+
series3: Object.freeze({ type: "color" }),
|
|
797
|
+
series4: Object.freeze({ type: "color" }),
|
|
798
|
+
series5: Object.freeze({ type: "color" }),
|
|
799
|
+
series6: Object.freeze({ type: "color" }),
|
|
800
|
+
axisColor: Object.freeze({ type: "color", uiDefault: "colors.onSurfaceMuted" }),
|
|
801
|
+
gridColor: Object.freeze({ type: "color", uiDefault: "colors.border" }),
|
|
802
|
+
barRadius: Object.freeze({ type: "size", min: 0, max: 24, uiDefault: "radii.sm" }),
|
|
803
|
+
}),
|
|
804
|
+
targets: Object.freeze({}),
|
|
805
|
+
}),
|
|
782
806
|
});
|
|
783
807
|
|
|
784
808
|
// sc-4505 — the value types a widget event payload field may declare. Narrower
|
|
@@ -2531,7 +2555,8 @@ const HOOKS = [
|
|
|
2531
2555
|
"Returns { groups, loading, error, refetch, create, remove, addMember, " +
|
|
2532
2556
|
"removeMember }. list returns the { data, meta } envelope verbatim — the " +
|
|
2533
2557
|
"hook unwraps res.data; rows are snake_case. Reads need groups.read:*; " +
|
|
2534
|
-
"mutations need groups.write:*."
|
|
2558
|
+
"mutations need groups.write:*. query { q?, ids?, limit?, offset? }: " +
|
|
2559
|
+
"`ids` (1 to 100) names the groups behind ids you already hold.",
|
|
2535
2560
|
returnShape: {
|
|
2536
2561
|
groups: "Array<{ id, name, member_count }> // snake_case rows; unwrapped from { data, meta }",
|
|
2537
2562
|
loading: "boolean",
|
|
@@ -3163,14 +3188,14 @@ const PRIMITIVES = [
|
|
|
3163
3188
|
{
|
|
3164
3189
|
name: "ActivityIndicator",
|
|
3165
3190
|
description:
|
|
3166
|
-
"The raw platform spinner.
|
|
3191
|
+
"The raw platform spinner. For a loading state draw `<Spinner>` (below) instead, which wears the app's spinner colour and follows the reduced-motion setting. See https://reactnative.dev/docs/activityindicator.",
|
|
3167
3192
|
rnComponent: "ActivityIndicator",
|
|
3168
3193
|
docsUrl: "https://reactnative.dev/docs/activityindicator",
|
|
3169
3194
|
},
|
|
3170
3195
|
{
|
|
3171
3196
|
name: "Switch",
|
|
3172
3197
|
description:
|
|
3173
|
-
'Themed on/off toggle. `<Switch label="Email me updates" value={on} onValueChange={setOn} />` —
|
|
3198
|
+
'Themed on/off toggle. `<Switch label="Email me updates" value={on} onValueChange={setOn} />` — the default way to draw a setting that takes effect at once. It is the platform\'s own switch, so each host gives it its switch role, state and keys; its "on" track is the app\'s primary colour and its "off" track the input border (Design > Components > Inputs). `label` draws the text beside it and makes the text tappable too, `helper` a hint below, `error` a message in its place (or `true` to mark it without one), `disabled` stops it (a device greys it; on the web it is drawn at half strength unless you passed your own colours). Always pass `label`, or `accessibilityLabel` when a visible label already sits next to it. Controlled — `value` + `onValueChange(next)`. Every React Native `Switch` prop still works: a `trackColor` you pass overrides the theme for the states it names, and `thumbColor` colours the thumb. Use it rather than a toggle you draw from `Pressable` + a sliding `View`; when the request needs a toggle it cannot draw (two labelled sides such as Day and Night, an icon inside the thumb), build your own in the app\'s look with the switch role and its checked state.',
|
|
3174
3199
|
rnComponent: "Switch",
|
|
3175
3200
|
docsUrl: "https://reactnative.dev/docs/switch",
|
|
3176
3201
|
},
|
|
@@ -3269,14 +3294,14 @@ const PRIMITIVES = [
|
|
|
3269
3294
|
{
|
|
3270
3295
|
name: "Modal",
|
|
3271
3296
|
description:
|
|
3272
|
-
'Standard dialog. `<Modal visible={open} onRequestClose={() => setOpen(false)} title="Edit order" footer={<><Button variant="secondary" title="Cancel" onPress={() => setOpen(false)} /><Button title="Save" onPress={save} /></>}>…body…</Modal>`.
|
|
3297
|
+
'Standard dialog. `<Modal visible={open} onRequestClose={() => setOpen(false)} title="Edit order" footer={<><Button variant="secondary" title="Cancel" onPress={() => setOpen(false)} /><Button title="Save" onPress={save} /></>}>…body…</Modal>`. The default way to open a dialog, a form in a pop-up, a detail panel or a bottom sheet: it draws the title, the close button, a body that scrolls when it is long, and a footer action row laid out below it, and its background, corner radius, padding, scrim, shadow and title colour come from the theme (Design > Components > Dialogs), so every dialog in the app matches. It is built on `<Overlay>`, so it renders OUTSIDE the widget layout box on both hosts and nothing can clip it. Use it rather than a header, a close button and an action row of your own inside a bare `<Overlay>`; when the request needs a dialog it cannot draw (a photo that bleeds to the panel edges above the title, a panel with no title bar and a split layout), build that panel inside a bare `<Overlay>` in the app\'s look, with a translated close control and `onRequestClose` wired. Keep a bare `<Overlay>` for surfaces that are not a dialog (a media or document preview, a lightbox). Props: `visible` (boolean), `onRequestClose` (fired by the close button, the backdrop press, the Escape key on web and the Android back button — always wire it), `dismissible` (default true; false hides the close button and stops the backdrop, Escape and Android back from closing it, for a dialog that must stay open while it saves — only while that work runs, and always restore it, or nobody can close the dialog), `title` (string), `footer` (the action buttons, laid out as one right-aligned row that wraps on a phone; omit for no footer), `presentation` ("dialog", centred, the default | "sheet", anchored to the bottom edge; keep a form with text inputs in "dialog" — in the exported app the keyboard handling of a sheet is not yet verified on a device), `size` ("sm" | "md" | "lg" | "full", default "md"), `dismissOnBackdropPress` (default true), `role` ("dialog" default | "alertdialog"; web only), `initialFocusRef` (a ref to the control focused on open; web only), `closeAccessibilityLabel` (default "Close" — pass your translated label), `accessibilityLabel` (names a dialog with no title) and `testID`. With no `onRequestClose` it draws no close button. Components rendered inside it read a theme re-inked for the background the dialog paints, so build the body as its own component. Do not wrap the body in your own `ScrollView` and do not re-style the panel.',
|
|
3273
3298
|
rnComponent: null,
|
|
3274
3299
|
docsUrl: null,
|
|
3275
3300
|
},
|
|
3276
3301
|
{
|
|
3277
3302
|
name: "ConfirmDialog",
|
|
3278
3303
|
description:
|
|
3279
|
-
'Standard confirmation prompt, a preset of `<Modal>`. `<ConfirmDialog visible={!!pending} tone="destructive" title="Delete this order?" message="This cannot be undone." confirmLabel={t("delete", "Delete")} onCancel={() => setPending(null)} onConfirm={async () => { await remove(pending.id); setPending(null); }} />`.
|
|
3304
|
+
'Standard confirmation prompt, a preset of `<Modal>`. `<ConfirmDialog visible={!!pending} tone="destructive" title="Delete this order?" message="This cannot be undone." confirmLabel={t("delete", "Delete")} onCancel={() => setPending(null)} onConfirm={async () => { await remove(pending.id); setPending(null); }} />`. The default way to ask "are you sure?" before a delete or any other action that cannot be undone, so every confirmation in the app has the same wording slots, button order and colours. Use it rather than a `<Modal>` with your own two buttons; when the question needs more than it draws (typing the record name to confirm, a tick box such as Also delete its files), build it as a `<Modal>` with a Cancel then a Confirm `<Button>` in its footer, Cancel the safe default and a second press ignored while the action runs. Never arm a button to be pressed twice, and never use `Alert.alert` or `window.confirm`. Props: `visible` (boolean), `title` (string, the question), `message` (what confirming does; text or a node), `confirmLabel` and `cancelLabel` (default the app\'s translated "Confirm" and "Cancel" — name the action: "Delete", "Remove", "Send"), `tone` ("default" | "destructive"; destructive draws the confirm button from the theme\'s Danger buttons colours and opens with focus on Cancel), `busy` (boolean; disables both buttons, shows a `<Spinner>` and keeps the dialog open), `onConfirm` (return the promise of the action and the dialog stays busy until it settles, with a second press ignored; a rejection is NOT caught, so catch a failure yourself and report it), `onCancel` (fired by the Cancel button, the close button, the backdrop press, the Escape key on web and the Android back button — cancelling is always the safe default), `closeAccessibilityLabel` and `testID`. The dialog never closes itself: set `visible` false from `onCancel` and after the action. Its panel follows the theme (Design > Components > Dialogs) and its buttons the Buttons and Destructive settings — do not re-style it.',
|
|
3280
3305
|
rnComponent: null,
|
|
3281
3306
|
docsUrl: null,
|
|
3282
3307
|
},
|
|
@@ -3285,7 +3310,7 @@ const PRIMITIVES = [
|
|
|
3285
3310
|
{
|
|
3286
3311
|
name: "EmojiPicker",
|
|
3287
3312
|
description:
|
|
3288
|
-
"In-app emoji picker. `<EmojiPicker visible={open} onRequestClose={() => setOpen(false)} onSelect={(char) => insert(char)} recents={recents} onRecentsChange={setRecents} />`.
|
|
3313
|
+
"In-app emoji picker. `<EmojiPicker visible={open} onRequestClose={() => setOpen(false)} onSelect={(char) => insert(char)} recents={recents} onRecentsChange={setRecents} />`. The default way to let an app user pick an emoji without leaving for the OS keyboard — for a message/post composer, and for starting a reaction. It presents inside the SDK `<Overlay>`, so it escapes the clipping box of the widget on both hosts, and it ships a curated Unicode set WITH the SDK (categories, search, recents) so no emoji dependency is needed. Props: `visible` (boolean), `onSelect` (`(char: string) => void`, fired with the chosen emoji character), `onRequestClose` (wire it or the picker cannot be dismissed), `recents` (string[] of previously picked characters) and `onRecentsChange` (persist them where the widget keeps its own state), plus `accessibilityLabel` and `searchPlaceholder`. To insert into a composer at the caret rather than appending, pair it with `insertAtCaret(value, char, selection)`. Typed emoji need nothing: a plain `TextInput` already stores and renders them.",
|
|
3289
3314
|
rnComponent: null,
|
|
3290
3315
|
docsUrl: null,
|
|
3291
3316
|
},
|
|
@@ -3304,63 +3329,78 @@ const PRIMITIVES = [
|
|
|
3304
3329
|
{
|
|
3305
3330
|
name: "MarkdownInput",
|
|
3306
3331
|
description:
|
|
3307
|
-
'Formatted-content editor. `<MarkdownInput value={draft} onChange={setDraft} />`.
|
|
3332
|
+
'Formatted-content editor. `<MarkdownInput value={draft} onChange={setDraft} />`. The default way to let an app USER write formatted text: a multi-line field, a toolbar whose buttons wrap the current SELECTION in markdown markers (bold, italic, code, H2, H3, bullets, numbered list), a Table button that inserts a pipe-table skeleton with the first header cell selected, a Link button that opens a form for the link text plus its target, and a live `<RichText>` preview of the result underneath. Use it rather than a bare `TextInput` plus formatting buttons of your own; an editor you do build (one that only bolds, a checklist editor) still stores markdown markers and previews with `<RichText>` — buttons that splice `<strong>`/`<br>`/`<table>` tags into the string leave the author reading tag soup with no preview of the real output. The stored value is markdown text. Props: `value`, `onChange(next)`, `placeholder`, `previewLabel` (default "Preview"), `showPreview` (default true), `renderImage` (passed through to the preview), `pages` (optional `[{ id, name }]` — supply it and the Link form offers those pages to link to by name, the way `renderImage` is supplied for filestore images; without it the form offers only an external address, because a widget cannot know the app\'s pages on its own), `minHeight` (default 150) and `maxHeight` (default 280) so the field scrolls internally instead of growing without bound, `accessibilityLabel`, `style`, `testID`. Links in the preview are deliberately inert, so a tap while editing cannot navigate away from the draft.',
|
|
3308
3333
|
rnComponent: null,
|
|
3309
3334
|
docsUrl: null,
|
|
3310
3335
|
},
|
|
3311
3336
|
{
|
|
3312
3337
|
name: "List",
|
|
3313
3338
|
description:
|
|
3314
|
-
"Themed list container. `<List>{rows.map((r) => <ListRow key={r.id} title={r.name} />)}</List>`. Spaces and insets its `<ListRow>` children the way the app's look says (flush rows with hairlines, or separated rounded rows), and hands any row override you pass (`rowPadding`, `rowMinHeight`, `gap`, `radius`, `background`, `dividerColor`, `dividerWidth`) down to every row. `<List variant=\"card\">` draws EVERY row as its own card instead:
|
|
3339
|
+
"Themed list container. `<List>{rows.map((r) => <ListRow key={r.id} title={r.name} />)}</List>`. Spaces and insets its `<ListRow>` children the way the app's look says (flush rows with hairlines, or separated rounded rows), and hands any row override you pass (`rowPadding`, `rowMinHeight`, `gap`, `radius`, `background`, `dividerColor`, `dividerWidth`) down to every row. `<List variant=\"card\">` draws EVERY row as its own card instead: the default way to build a list of cards (one record per card, stacked in one column). Each card's surface (fill, border, corner radius, padding, shadow, gradient) is the app's card look (Design > Components > Cards), what is inside it (height, the leading / body / trailing arrangement) is its Lists look (Design > Components > Lists), and the cards are spaced by the Lists gap, or a small step when that is unset; prefer it to wrapping rows of your own in `<Card>`s. `defaults` takes the same keys as the overrides and is used only where the app's Lists settings leave a value unset (a built-in widget keeps its own row look with it); leave it out in a new widget so the app decides. Props: those overrides, `variant` (\"plain\" default | \"card\"), `defaults`, `style`, `testID`. Put it inside a `ScrollView` (or bound its height) for long lists.",
|
|
3315
3340
|
rnComponent: null,
|
|
3316
3341
|
docsUrl: null,
|
|
3317
3342
|
},
|
|
3318
3343
|
{
|
|
3319
3344
|
name: "ListRow",
|
|
3320
3345
|
description:
|
|
3321
|
-
'Themed list row. `<ListRow title={item.name} subtitle={item.email} leading={<Icon name="user" size={20} />} trailing={<Text>…</Text>} onPress={() => open(item)} selected={item.id === selectedId} />`.
|
|
3346
|
+
'Themed list row. `<ListRow title={item.name} subtitle={item.email} leading={<Icon name="user" size={20} />} trailing={<Text>…</Text>} onPress={() => open(item)} selected={item.id === selectedId} />`. The default way to draw one row of records: its padding, minimum height, divider, corner radius and background come from the theme\'s Lists settings, so every list in the app matches. Use it rather than a row of your own from `View` + `Pressable`; when the request needs a row it cannot lay out (a chat bubble, a timeline entry on a rail), draw your own with the theme\'s spacing, surface and divider colours. Props: `title`, `subtitle` (rendered in the subtitle / caption text tiers), `leading` / `trailing` nodes, `children` (replaces the title block: a single `View` child — your own row of cells — spans the full width between `leading` and `trailing`; anything else, such as several cells, sits in the row as given, which is the shape a table row takes under a `<ListHeader>`), `onPress` (makes it a button with a press state), `actions` (the row\'s OWN buttons, such as remove, move up or sign: drawn after `trailing` and, on a row with `onPress`, kept OUTSIDE its tap target so each stays a separate button the keyboard and a screen reader reach; never put a `Pressable` in `trailing` of a pressable row), `selected`, `disabled`, `divider` (default true; pass false on the last row), `accessibilityLabel`, `style`, `testID`, `defaults`, plus the same per-row overrides `<List>` takes. Inside a `<List variant="card">` the row is drawn as one card. Declare `listRowPadding` / `listRowRadius` / … style fields and pass them through to let an author override one widget.',
|
|
3322
3347
|
rnComponent: null,
|
|
3323
3348
|
docsUrl: null,
|
|
3324
3349
|
},
|
|
3325
3350
|
{
|
|
3326
3351
|
name: "ListHeader",
|
|
3327
3352
|
description:
|
|
3328
|
-
"Themed column header for a table of `<ListRow>`s. `<ListHeader><Text style={[overline, { flex: 2, minWidth: 0 }]}>Name</Text><Text style={[overline, { flex: 1, minWidth: 0 }]}>Status</Text></ListHeader>` above `<List>{rows.map((r) => <ListRow key={r.id}><Text numberOfLines={1} style={[body, { flex: 2, minWidth: 0 }]}>{r.name}</Text><Text numberOfLines={1} style={[body, { flex: 1, minWidth: 0 }]}>{r.status}</Text></ListRow>)}</List>`.
|
|
3353
|
+
"Themed column header for a table of `<ListRow>`s. `<ListHeader><Text style={[overline, { flex: 2, minWidth: 0 }]}>Name</Text><Text style={[overline, { flex: 1, minWidth: 0 }]}>Status</Text></ListHeader>` above `<List>{rows.map((r) => <ListRow key={r.id}><Text numberOfLines={1} style={[body, { flex: 2, minWidth: 0 }]}>{r.name}</Text><Text numberOfLines={1} style={[body, { flex: 1, minWidth: 0 }]}>{r.status}</Text></ListRow>)}</List>`. The default way to label the columns of a table of `<ListRow>`s: it takes the SAME horizontal padding, gap and side edges as `<ListRow>` from the theme's Lists settings, so a label given the same `flex` as its row cell sits exactly over that column, where a header `View` with its own padding cannot line up with the rows. Give the header and the rows the same shape (the same cells as children, each column the same `flex`, `minWidth: 0` on every cell). Props: `children` (the labels), `leading` / `trailing` (a spacer as wide as the rows' leading / trailing node, so the columns still line up), `divider` (default true), `accessibilityLabel`, `style`, `testID`, and the list overrides that shape it (`rowPadding`, `gap`, `dividerColor`, `dividerWidth`) — pass it the SAME overrides you pass the `<List>`. To keep it in view, make it the FIRST child of the rows' `ScrollView` with `stickyHeaderIndices={[0]}`, and give it the surface it sits on (`style={{ backgroundColor: theme.colors.surface }}`) so rows do not show through it: inside the scroller it shares the rows' width beside a scrollbar, which a header outside it cannot.",
|
|
3329
3354
|
rnComponent: null,
|
|
3330
3355
|
docsUrl: null,
|
|
3331
3356
|
},
|
|
3332
3357
|
{
|
|
3333
3358
|
name: "EmptyState",
|
|
3334
3359
|
description:
|
|
3335
|
-
'Themed empty / zero-data state. `<EmptyState icon="inbox" title="No orders yet" message="New orders appear here." />`.
|
|
3360
|
+
'Themed empty / zero-data state. `<EmptyState icon="inbox" title="No orders yet" message="New orders appear here." />`. The default way to say "nothing here" — a muted panel with an icon, a title and a caption, centred, in the app\'s own surface and text tiers. Props: `icon` (a lucide icon name, default "inbox"; pass null for none), `title`, `message`, `children` (an action, e.g. a button), `style`, `testID`.',
|
|
3336
3361
|
rnComponent: null,
|
|
3337
3362
|
docsUrl: null,
|
|
3338
3363
|
},
|
|
3339
3364
|
{
|
|
3340
3365
|
name: "Badge",
|
|
3341
3366
|
description:
|
|
3342
|
-
'Themed status pill. `<Badge label={row.status} tone={row.status === "Overdue" ? "danger" : "success"} />`.
|
|
3367
|
+
'Themed status pill. `<Badge label={row.status} tone={row.status === "Overdue" ? "danger" : "success"} />`. The default way to show a record\'s STATE (Active, Paid, Overdue, New) beside the thing it describes: in a `<ListRow>` trailing slot, next to a title, in a detail header. The label always renders, so the meaning never rides on colour alone. Keep it to one to three words; a name, amount, date or sentence is plain text, not a badge. Its fill and text colour come from the app\'s theme per tone, so prefer it to a pill of your own from `View` + radius + a hex. Props: `label` (string or number), `tone` ("neutral" | "info" | "success" | "warning" | "danger", default "neutral"), `icon` (optional lucide name before the label), `style`, `testID`. It hugs its label (`alignSelf: "flex-start"`); pass `style={{ alignSelf: "auto" }}` to let the parent place it.',
|
|
3343
3368
|
rnComponent: null,
|
|
3344
3369
|
docsUrl: null,
|
|
3345
3370
|
},
|
|
3346
3371
|
{
|
|
3347
3372
|
name: "Avatar",
|
|
3348
3373
|
description:
|
|
3349
|
-
'Themed picture of a person. `<Avatar name={row.owner.label} url={photoUrl} size="sm" />`.
|
|
3374
|
+
'Themed picture of a person. `<Avatar name={row.owner.label} url={photoUrl} size="sm" />`. The default way to show a PERSON: in a `<ListRow>` leading slot, a profile header, a list of members. It falls back from the picture to the name\'s initials to a person glyph, so the slot is never blank. Never use it for a place, product or topic. Props: `name` (the initials and the accessibility label come from it), `seed` (a stable id such as the user\'s id: the initials sit on that person\'s own colour, the same one the Studio\'s member lists use, so people in a list tell apart at a glance; `avatarColorFor(seed)` returns it), `url` (an absolute image URL), `fileId` (a filestore file id such as a FILE column\'s value, preferred over `url` for a stored photo: it loads the right size, shows the initials while loading and keeps them if the photo cannot load, and wins when both are set), `size` ("sm" 32 | "md" 40 | "lg" 56 | "xl" 80, or a number of px), `shape` ("circle" | "rounded"), `accessibilityLabel`, `style`, `testID`.',
|
|
3375
|
+
rnComponent: null,
|
|
3376
|
+
docsUrl: null,
|
|
3377
|
+
},
|
|
3378
|
+
// A stored file's picture with the loading placeholder and fallback built in.
|
|
3379
|
+
{
|
|
3380
|
+
name: "FileImage",
|
|
3381
|
+
description:
|
|
3382
|
+
'Themed picture of a stored file. `<FileImage fileId={record.Photo} variant="card" aspectRatio={4 / 3} />`. Use it by default to show a FILE column or any filestore file id as a picture: it resolves the id with `useFilestoreFile`, loads the rung of the size ladder `variant` names, draws a `<Skeleton>` (Design → Components → Loading) while it loads, fetches an expired signed URL again once, and shows a fallback instead of a broken image when the file is missing or will not load. Pass `uri` instead of `fileId` for a URL you already hold, such as a `useFilestoreFiles()` row\'s `urls.card` (no per-row fetch; a failed load shows the fallback). For a PERSON use `<Avatar fileId>`. When the request needs what it cannot do, such as zooming or panning, cropping by hand, or an overlay drawn on the picture, build your own from `useFilestoreFile` and `<Image>`. Props: `fileId`, `uri`, `variant` ("thumbnail" 128 | "card" 512 | "large" 1024, the default | "hero" 2048 | "original"; pick the next rung up from the rendered size), `aspectRatio` (width / height; unset, the box takes the picture\'s own shape once it loads, so pass one in a grid to keep rows even), `radius` (px, a theme radius name, or "none"; default the theme\'s `md`), `resizeMode` ("cover" default | "contain" | "stretch" | "center"), `fallback` ("icon" default | "blank"), `accessibilityLabel` (unset, the picture is decorative), `style` (the box: give it a width, or a width and height), `testID` (also tags `<testID>-image`, `<testID>-loading`, `<testID>-fallback`).',
|
|
3350
3383
|
rnComponent: null,
|
|
3351
3384
|
docsUrl: null,
|
|
3352
3385
|
},
|
|
3353
3386
|
{
|
|
3354
3387
|
name: "Progress",
|
|
3355
3388
|
description:
|
|
3356
|
-
'Themed completion bar or ring. `<Progress value={(done / total) * 100} label="Sessions booked" />`.
|
|
3389
|
+
'Themed completion bar or ring. `<Progress value={(done / total) * 100} label="Sessions booked" />`. The default way to show real progress toward a known goal. `value` is clamped to 0-100, and the percent renders as text unless `showValue={false}`. Never decoration, a rating or a score. Props: `value`, `variant` ("bar" | "ring", default "bar"), `label`, `showValue` (default true), `tone` ("primary" | "success" | "warning" | "danger", default "primary"), `style`, `testID`.',
|
|
3357
3390
|
rnComponent: null,
|
|
3358
3391
|
docsUrl: null,
|
|
3359
3392
|
},
|
|
3360
3393
|
{
|
|
3361
3394
|
name: "Stat",
|
|
3362
3395
|
description:
|
|
3363
|
-
'Themed headline figure. `<Stat value={openCount} label="Open orders" delta="+3 this week" deltaTone="positive" icon="package" />`.
|
|
3396
|
+
'Themed headline figure. `<Stat value={openCount} label="Open orders" delta="+3 this week" deltaTone="positive" icon="package" />`. The default way to show one headline number in a summary tile; put peer tiles side by side in a row. It only DISPLAYS a figure: compute a count, sum or average over a table with `useDatastoreFigure` and pass its `text`. An empty value (null, undefined, blank text, NaN) prints `emptyValue` (default "—") in the muted tier, never a fake 0. Props: `value` (string or number), `label`, `icon` (lucide name), `delta`, `deltaTone` ("neutral" | "positive" | "negative"), `emptyValue`, `color` and `fontSize` (12-96) for the figure, `style`, `testID`. In a tile too narrow for the icon, figure and delta on one line, they wrap onto the next line rather than run past the tile.',
|
|
3397
|
+
rnComponent: null,
|
|
3398
|
+
docsUrl: null,
|
|
3399
|
+
},
|
|
3400
|
+
{
|
|
3401
|
+
name: "Chart",
|
|
3402
|
+
description:
|
|
3403
|
+
'Themed chart. `<Chart type="bar" data={groups.map((g) => ({ label: String(g.group), value: Number(g.sum) || 0 }))} loading={loading} valueFormat="compact" />`. Use it by default to plot a table\'s figures: feed it ONE `useDatastoreAggregate` call (`groups` for a breakdown by a column, `buckets` for a series over time) mapped to `[{ label, value }]`. `type` is "bar" (default), "hbar" (horizontal bars, for long category names), "line", "area", "donut" (the parts of a whole) or "sparkline" (a small line inside a tile, with no axes). It measures its own width and redraws when its container resizes, shows a `<Skeleton>` while `loading` and an `<EmptyState>` when there is nothing to plot, draws a legend for a donut and for several series, and gives a screen reader a one-line summary followed by the values. Its series colours, axis and label colour, gridline colour and bar corners come from the app\'s chart look (Design > Components > Charts), falling back to the theme\'s colours, so pass no colours of your own. Props: `data` (`[{ label, value }]`, or one number per series key), `series` (`[{ key, label, color? }]` for several series sharing one `data`: bars stand side by side, lines and areas overlay; a donut and a sparkline draw the first), `type`, `valueFormat` ("number" default | "compact", 1.2k | "percent", which appends % to a value already in percent, 42 reads 42% | a function of the number; used for the axis, the legend and the spoken values, grouped for the app\'s language), `height` (px, default 220; 48 for a sparkline), `loading`, `emptyTitle` (default the shared "No results" text) and `emptyMessage`, `accessibilityLabel` (replaces the spoken summary sentence; pass your translated title), `moreText` (`(count) => string`, the tail of a long spoken value list, default "and N more"; pass it translated), `style`, `testID`. When the request needs a chart it cannot draw (a scatter plot, a gantt or timeline, brushing or tapping a bar to filter), build your own with react-native-svg in the app\'s look (see DRAWING A CHART), painting it with `chartColors(useTheme())`: `series(i)` and `seriesOpacity(i)` for the i-th series, `axis` and `grid`, the colours `<Chart>` uses.',
|
|
3364
3404
|
rnComponent: null,
|
|
3365
3405
|
docsUrl: null,
|
|
3366
3406
|
},
|
|
@@ -3371,17 +3411,24 @@ const PRIMITIVES = [
|
|
|
3371
3411
|
rnComponent: null,
|
|
3372
3412
|
docsUrl: null,
|
|
3373
3413
|
},
|
|
3414
|
+
{
|
|
3415
|
+
name: "Grid",
|
|
3416
|
+
description:
|
|
3417
|
+
'Balanced lines of cells sized from the grid\'s own measured width. `<Grid minCellWidth={240}>{items.map((item) => <Card key={item.id} style={{ flex: 1 }}>…</Card>)}</Grid>` — use it by default for a card grid, a tile or stat row, and a grid of definite-size cells (calendar days, seats, swatches); when the layout needs what it cannot express (masonry, a bento layout whose cells span lines), lay it out yourself with `useContainerWidth()`. It measures an inner box with no padding, so padding in its `style` is never counted into the width. `balance` "cards" (default): as many cells per line as fit at `minCellWidth` (240 when unset), every line but the last full and the last padded with empty cells so its cards keep the width above (6 that fit 5 are 3 + 3). "tiles": the remainder spreads over every line and each line stretches (5 at 4 per line are 3 + 2, never 4 + 1); the per-line ceiling is `maxColumns`, or what fits at `minCellWidth`, or else 4 / 3 / 2 for the wide / medium / narrow width tier. `columns` fixes the count per line. `cell={{ min, max }}` makes definite-size cells as large as fit up to `max` (never below `min`, 32 when unset) in a block centred in the grid (a block whose `min` cells cannot fit starts at the leading edge, so only its far end is cut off), `square` makes them as tall as wide: `<Grid columns={7} cell={{ min: 32, max: 64 }} square gap="xs">`. Props: `children` (one cell each; each keeps its own look and theme scope, and a child with `flex: 1` fills its cell\'s height so a line of cards ends level), `balance`, `minCellWidth`, `maxColumns`, `columns`, `cell`, `square`, `gap` (a theme spacing name, default "md", or a number), `style` (the outer box), `testID`, `accessibilityLabel`. A layout that depends on the width draws nothing until it is measured. In a row, give it `style={{ flex: 1, minWidth: 0 }}` so it has a width to measure.',
|
|
3418
|
+
rnComponent: null,
|
|
3419
|
+
docsUrl: null,
|
|
3420
|
+
},
|
|
3374
3421
|
{
|
|
3375
3422
|
name: "Banner",
|
|
3376
3423
|
description:
|
|
3377
|
-
'Themed inline message. `<Banner tone="error" title="Could not save" message={err.message} action={{ label: "Retry", onPress: save }} onDismiss={() => setErr(null)} />`.
|
|
3424
|
+
'Themed inline message. `<Banner tone="error" title="Could not save" message={err.message} action={{ label: "Retry", onPress: save }} onDismiss={() => setErr(null)} />`. The default way to show an error, warning, success or notice that belongs to a place in the widget and stays until it is resolved: a list that failed to load, a form the user cannot submit yet, a "read-only" or "offline" notice. Render it where the problem is — above the list or form it concerns. Use `useToast()` instead for a brief confirmation of something the user just did ("Saved", "Deleted") that needs no decision and may disappear on its own; never both for one event. Its fill, text colour, border and corner radius come from the app\'s theme per tone (Design > Components > Alerts), so prefer it to a tinted box of your own from `View` + a border + a hex; when the request needs a notice it cannot draw (an illustration beside the text, a small form inside the notice), build it from the theme status colours (`colors.danger`, `colors.warning`, `colors.success`, `colors.info`), with the alert role for an error. Props: `tone` ("info" default | "success" | "warning" | "error"), `title` (optional, one short line) and `message` (wraps — never truncate it): text, or an element you built when part of it needs its own styling, `icon` (a lucide name replacing the tone\'s own glyph, or `false` for none), `action` (`{ label, onPress, accessibilityLabel?, testID? }`, ONE action such as Retry), `onDismiss` (pass it to show a close control; you hide the banner in the handler), `dismissAccessibilityLabel` (default "Dismiss" — pass a translated one), `style` (spacing around it), `testID`. An `error` is marked as an alert, and the other tones as a polite status: the web and Android announce them when they appear, while iOS reads the banner in place.',
|
|
3378
3425
|
rnComponent: null,
|
|
3379
3426
|
docsUrl: null,
|
|
3380
3427
|
},
|
|
3381
3428
|
{
|
|
3382
3429
|
name: "Accordion",
|
|
3383
3430
|
description:
|
|
3384
|
-
'Themed collapsible sections. `<Accordion defaultValue="shipping"><AccordionItem value="shipping" title="Shipping" subtitle="2-4 days">…</AccordionItem><AccordionItem value="returns" title="Returns">…</AccordionItem></Accordion>`.
|
|
3431
|
+
'Themed collapsible sections. `<Accordion defaultValue="shipping"><AccordionItem value="shipping" title="Shipping" subtitle="2-4 days">…</AccordionItem><AccordionItem value="returns" title="Returns">…</AccordionItem></Accordion>`. The default way to build a section that expands and collapses (an FAQ, a settings group, a record\'s details, a group whose members show on demand): the surface takes the app\'s card look (Theme Settings > Cards: background, border, corner radius) and the headers and dividers its list look (Theme Settings > Lists: row padding, row height, divider), so every collapsible section in the app matches. Each header has a chevron, a 44px touch target, works from the keyboard and tells assistive technology whether its section is open, so prefer it to a collapsible of your own from `Pressable` + a chevron + a `useState`; when the request needs one it cannot draw (a header that is a whole photo card, a section that opens sideways), build it in the app\'s look with a 44px header that carries the button role and its expanded state. By default one section is open at a time and pressing the open one closes it; `multiple` lets several stay open. Props: `multiple`, `value` (the open key, or an array of keys when `multiple`; makes it controlled), `defaultValue` (the same shape, uncontrolled), `onChange(next)` (the open key(s) after a press, in the same shape), `accessibilityLabel`, `style`, `testID`. Its children are `<AccordionItem>`s.',
|
|
3385
3432
|
rnComponent: null,
|
|
3386
3433
|
docsUrl: null,
|
|
3387
3434
|
},
|
|
@@ -3395,63 +3442,84 @@ const PRIMITIVES = [
|
|
|
3395
3442
|
{
|
|
3396
3443
|
name: "Tabs",
|
|
3397
3444
|
description:
|
|
3398
|
-
'Themed tab strip. `<Tabs items={[{ key: "open", label: "Open" }, { key: "done", label: "Done", icon: "check" }]} value={tab} onChange={setTab} />`.
|
|
3445
|
+
'Themed tab strip. `<Tabs items={[{ key: "open", label: "Open" }, { key: "done", label: "Done", icon: "check" }]} value={tab} onChange={setTab} />`. The default way to switch between views inside a widget: its look (a segmented control, pills, or an underline — Theme Settings > Tabs) and its colours and corner radius come from the theme, so every tab strip in the app matches, including the page Tabs container. It draws the strip only; render the selected panel yourself below it. Use it rather than tabs of your own from `Pressable` chips; when the request needs a strip it cannot draw (a live count badge on each tab, a vertical rail down the side), build your own in the app\'s look with the tab role and its selected state. Props: `items` (`[{ key, label, icon?, disabled?, testID? }]`, icon is a lucide name), `value` (the selected key), `onChange(key)`, `accessibilityLabel`, `style`, `testID`, and the overrides `variant` ("segmented" | "pill" | "underline"), `activeColor`, `activeTextColor`, `textColor`, `radius`. The strip scrolls sideways when the tabs overflow. Declare `tabsStyle` / `tabsActiveColor` / `tabsActiveTextColor` / `tabsTextColor` / `tabsRadius` style fields and pass them through to let an author override one widget.',
|
|
3399
3446
|
rnComponent: null,
|
|
3400
3447
|
docsUrl: null,
|
|
3401
3448
|
},
|
|
3402
3449
|
{
|
|
3403
3450
|
name: "Chip",
|
|
3404
3451
|
description:
|
|
3405
|
-
'Themed chip. `<ChipGroup selection="single">{STATUSES.map((s) => <Chip key={s} label={s} selected={status === s} onPress={() => setStatus(s)} />)}</ChipGroup>`.
|
|
3452
|
+
'Themed chip. `<ChipGroup selection="single">{STATUSES.map((s) => <Chip key={s} label={s} selected={status === s} onPress={() => setStatus(s)} />)}</ChipGroup>`. The default way to build filter chips, tags and pills: anything small, rounded and tappable that selects, filters or can be removed. ONE facet with up to five options may be a single-choice `<ChipGroup>` of all its options; several facets, or a longer option list, go in the filter toolbar, where only the ACTIVE filters show as removable chips. Its fill, text colour, border, corner radius and selected colours come from the app\'s chip look (Theme Settings > Chips), so every chip in the app matches. Use it rather than one of your own from `Pressable` + `radii.pill` + a fill; when the request needs a chip it cannot draw (a colour swatch, a photo thumbnail beside the label), build it with the theme radius and colours and a 44px touch target on a narrow screen. A display-only STATE (Active, Paid, Overdue) is a `<Badge>`, not a chip. Props: `label` (a string or a number; it truncates on one line instead of breaking the row, or pass `numberOfLines={0}` to let it wrap), `icon` (optional lucide name before the label), `selected` (boolean; pass it together with `onPress` for a chip that toggles, and keep the selection in your own state), `onPress`, `onRemove` (adds a trailing x button, e.g. an active filter or a tag the user can drop; it is a separate button from `onPress`), `disabled`, `size` ("md" default, a full 44px touch target: use it on a narrow screen | "sm", a compact 28px chip for a medium or wide toolbar), `variant` ("filled" default | "outlined", a bordered chip on the surface), `accessibilityLabel`, `removeAccessibilityLabel` (default "Remove <label>"; pass your translated text), `style`, `testID`, and the overrides `background`, `textColor`, `borderColor`, `radius`, `selectedBackground`, `selectedTextColor` (hex colours; leave them unset so the app\'s chip look applies; a fill set without its text colour gets the dark or light label that reads on it). Inside a `<ChipGroup>` with a `selection`, every chip with `onPress` is a choice whether picked or not: a radio in a single-choice group, otherwise a checkbox, with its checked state; outside one, a chip given a boolean `selected` is a checkbox and a chip with only `onPress` is a button. On the web a single-choice group is one tab stop and the arrow keys move the choice.',
|
|
3406
3453
|
rnComponent: null,
|
|
3407
3454
|
docsUrl: null,
|
|
3408
3455
|
},
|
|
3409
3456
|
{
|
|
3410
3457
|
name: "ChipGroup",
|
|
3411
3458
|
description:
|
|
3412
|
-
'Wrapping row of `<Chip>`s. `<ChipGroup selection="multiple" accessibilityLabel="Tags">…</ChipGroup>` spaces its chips and wraps them onto the next line on a narrow screen, so
|
|
3459
|
+
'Wrapping row of `<Chip>`s. `<ChipGroup selection="multiple" accessibilityLabel="Tags">…</ChipGroup>` spaces its chips and wraps them onto the next line on a narrow screen, so prefer it to a `flexWrap` row of your own. Props: `selection` ("none" default | "single", one choice at a time, so its chips are radios | "multiple", any number of choices), `gap` (px between chips; defaults to the theme\'s `spacing.xs`), `accessibilityLabel`, `style`, `testID`. It only lays out and announces: the selected value lives in your state and each chip gets its own `selected` and `onPress`.',
|
|
3413
3460
|
rnComponent: null,
|
|
3414
3461
|
docsUrl: null,
|
|
3415
3462
|
},
|
|
3416
3463
|
{
|
|
3417
3464
|
name: "Card",
|
|
3418
3465
|
description:
|
|
3419
|
-
'Themed card surface. `<Card>…</Card>` —
|
|
3466
|
+
'Themed card surface. `<Card>…</Card>` — the default way to draw a card, panel or tile: with no props it takes the app\'s card look (Theme Settings > Cards: background, border, corner radius, padding, shadow, gradient) so it matches every built-in card. Override one card with `background`, `borderColor`, `radius`, `padding` (a number or `{ top, right, bottom, left }`), `shadow` ("none" | "sm" | "md" | "lg") or `gradient` (`{ from, to, angle }`), or pass `style={props.style}` when your widget declares the `cardBackground` / `cardBorderColor` / `cardRadius` / `cardPadding` / `cardGradient` / `shadow` / `margin` style fields, so an author can restyle it. A card that brings no `baseStyle` also takes the box\'s LAYOUT from `style` — width, height, the flex properties, `alignSelf`, `gap`, position — so `style={{ width: \'100%\', maxWidth: 480, alignSelf: \'center\' }}` sizes the card and still keeps the app\'s card look; put anything else about the container on `containerStyle`. `style` may be an object or a React Native style array (`style={[{ flex: 1, minWidth: 0 }, align && { alignItems: align }]}`), read as the host flattens it: nested arrays and falsy entries are fine and a later entry wins. A gradient paints on both hosts. `image` (a URL or `{ uri }`) makes it a PHOTO card: the picture fills the card edge to edge BEHIND the content, under a light gradient on the edge named by `scrim` ("bottom" default | "top" | "left" | "right" | "full" | "none") — the edge the text sits on — and cropped around `imagePosition` ("center" default | "top" | "bottom" | "left" | "right"). A photo card has no picture-shaped content to size it, so give it an `aspectRatio` or `minHeight` in `style`, and pass the children as a function — `{({ colors, onImage }) => …}` — to receive the theme colours as they read where the content sits (`colors.onSurface` for a title, `colors.onSurfaceMuted` for a supporting line): light over a picture, the theme\'s own on a card without one. `image={null}` renders an ordinary card, so a record with no photo needs no branch. A photo card is a `View` or, with `as`, a `Pressable`; put a scroller inside it, never as it. Use it whenever an item has a photograph, instead of a small `Image` with its text in a box underneath. `as` renders the card as another container (e.g. `Pressable`, `ScrollView`) and every other prop lands on it. Use it rather than a card of your own from `View` + border + radius; a surface it cannot draw (a ticket with a torn edge, a card split into two coloured halves) is your own `View` in the theme surface, border, radius and elevation tokens.',
|
|
3420
3467
|
rnComponent: null,
|
|
3421
3468
|
docsUrl: null,
|
|
3422
3469
|
},
|
|
3423
3470
|
{
|
|
3424
3471
|
name: "Button",
|
|
3425
3472
|
description:
|
|
3426
|
-
'Themed button. `<Button title="Save" onPress={save} />` —
|
|
3473
|
+
'Themed button. `<Button title="Save" onPress={save} />` — use it by default for a button: its fill, label colour, border, corner radius, padding, weight, shadow and gradient come from the app\'s button look (Design > Components > Buttons, and Danger buttons / Button outline for the danger variant and emphasis border), so it matches the built-in Button widget. Props: `title`, `onPress` (omit it for a purely visual button inside something else that is pressable), `variant` ("primary" default | "secondary" | "outlined" | "danger" | "ghost", no fill or border with the accent as its label, for a quiet action such as "Load more" or "Cancel" | "link", underlined accent text with no side padding, for an inline action), `size` ("sm", 36px high | "md", 44px, the default | "lg", 52px), `busy` (an action in flight: a `<Spinner>` over the label, which stays the button\'s accessible name and keeps its width, and the button cannot be pressed; use it for a save or an upload instead of swapping the label), `selected` (a toggle\'s on state, drawn in the accent\'s quiet tint; omit it for a plain button), `icon` (a lucide name) with `iconPosition` ("leading" | "trailing"), `disabled`, `accessibilityLabel`, `testID`, `style` (extra container style, e.g. `alignSelf`) and `labelStyle`. Pass `fields` — `{ background, textColor, borderColor, borderWidth, paddingX, paddingY, radius, fontWeight, fontSize, shadow, gradient }` — to override one button, e.g. from style fields your widget declares. A ghost or link button takes the look\'s shape and type but not its fill, border, shadow or label colour, unless you pass them in `fields`. On a section that paints its own fill (`theme.colors.backdrop` there), a variant colour that the section makes unreadable takes that section\'s text colour, as its text does: an outlined label and border, a secondary whose fill disappears becomes outlined, and a primary or danger fill turns inverse (that text colour as its fill, its own colour as its label). A white or lightly tinted panel changes nothing, and a button inside a `<Card>` is measured against that card. A colour you pass in `fields`, `style` or `labelStyle`, or that the app\'s button look sets, is never changed. A button with only an icon is `<IconButton>`. For any other button, when the request needs one it cannot draw (a round floating action button, a tile with an image and two lines of text, a split button with a menu half), build your own from `Pressable` with `pressableLift` in the app\'s look (theme colours, radii and spacing), at least 44px high (a compact icon control in a dense row may go down to 36px, never below) and with an `accessibilityLabel`.',
|
|
3474
|
+
rnComponent: null,
|
|
3475
|
+
docsUrl: null,
|
|
3476
|
+
},
|
|
3477
|
+
{
|
|
3478
|
+
name: "IconButton",
|
|
3479
|
+
description:
|
|
3480
|
+
'Themed icon-only button. `<IconButton icon="pencil" accessibilityLabel="Edit order" onPress={edit} />` — use it by default for a button that shows only an icon (edit, delete, close, more, move up or down, a toolbar toggle): a real square on both hosts, 44px by default (size "sm", 36px, for a compact control in a dense row), the icon drawn with `<Icon>`, and the same button look, variants, `busy` and `disabled` as `<Button>`. Props: `icon` (a lucide name), `accessibilityLabel` (required: it is the button\'s only name for a screen reader, so name the action and its object, e.g. "Remove Anna"), `variant` ("ghost" default, no fill or border | "primary" | "secondary" | "outlined" | "danger"), `size` ("sm", 36px | "md", 44px, the default | "lg", 52px; on a device a smaller one still reaches 44px by touch slop), `color` (the glyph colour over the variant\'s, e.g. `theme.colors.danger` for a remove icon or `theme.colors.onSurfaceMuted` for a quiet toolbar), `selected` (a toggle such as Bold: the accent\'s quiet tint, announced as pressed), `busy` (a `<Spinner>` in place of the icon; it cannot be pressed), `disabled`, `onPress`, `fields`, `style` and `testID`. When the request needs what it cannot draw, such as an icon with a count badge or a photo as the button, build your own from `Pressable` in the app\'s look, at least 44px square (a compact icon control in a dense row may go down to 36px, never below) and with an `accessibilityLabel`.',
|
|
3427
3481
|
rnComponent: null,
|
|
3428
3482
|
docsUrl: null,
|
|
3429
3483
|
},
|
|
3430
3484
|
{
|
|
3431
3485
|
name: "TextField",
|
|
3432
3486
|
description:
|
|
3433
|
-
'Themed text-entry field. `<TextField value={name} onChangeText={setName} placeholder="Your name" accessibilityLabel="Name" />` — every `TextInput` prop works, and its background, text colour, border, corner radius and height come from the app\'s input look (Design > Components > Inputs), so a widget\'s fields match the built-in forms. `multiline` grows the field to a ceiling and then scrolls inside it. Override one field with `background`, `textColor`, `borderColor`, `radius` or `height`; `style` is merged last. `label` draws a label above the field and names it for a screen reader, `helper` a hint below, `error` a message in the helper\'s place with a danger border (`true` marks it without a message), `disabled` makes it read-only and dimmed; `containerStyle` styles the box around a labelled field. Use it instead of styling a bare `TextInput
|
|
3487
|
+
'Themed text-entry field. `<TextField value={name} onChangeText={setName} placeholder="Your name" accessibilityLabel="Name" />` — every `TextInput` prop works, and its background, text colour, border, corner radius and height come from the app\'s input look (Design > Components > Inputs), so a widget\'s fields match the built-in forms. `multiline` grows the field to a ceiling and then scrolls inside it. Override one field with `background`, `textColor`, `borderColor`, `radius` or `height`; `style` is merged last. `label` draws a label above the field and names it for a screen reader, `helper` a hint below, `error` a message in the helper\'s place with a danger border (`true` marks it without a message), `disabled` makes it read-only and dimmed; `containerStyle` styles the box around a labelled field. Use it by default instead of styling a bare `TextInput`; a field it cannot draw (a one-time code split into digit boxes, an amount with a stepper inside its border) is your own build that keeps a label, the input colours of the theme and an error message.',
|
|
3434
3488
|
rnComponent: null,
|
|
3435
3489
|
docsUrl: null,
|
|
3436
3490
|
},
|
|
3437
3491
|
{
|
|
3438
3492
|
name: "Checkbox",
|
|
3439
3493
|
description:
|
|
3440
|
-
'Themed tick box. `<Checkbox label="Remember me" value={checked} onValueChange={setChecked} />` —
|
|
3494
|
+
'Themed tick box. `<Checkbox label="Remember me" value={checked} onValueChange={setChecked} />` — the default way to draw a yes/no choice that is submitted with a form, or one row of a multi-select list. The box is the app\'s input surface (Design > Components > Inputs) and fills with the primary colour when checked; focus draws a ring in the primary colour around it. The whole row is one 44px target with the checkbox role and checked state on both hosts, and Space toggles it on the web. Props: `value` (boolean), `onValueChange(next)`, `label` (text, or an element), `helper`, `error` (a message, or `true`), `disabled`, `accessibilityLabel` (required when there is no text `label`), `style` (the row), `containerStyle` (the box around a row that shows a helper or error), `testID`; any other prop lands on the row. Use it rather than a tick box of your own from `Pressable` + a bordered `View` + a "✓"; when the request needs one it cannot draw (a tri-state box for some selected, a whole selectable card), build it in the theme colours with the checkbox role and its checked state.',
|
|
3441
3495
|
rnComponent: null,
|
|
3442
3496
|
docsUrl: null,
|
|
3443
3497
|
},
|
|
3444
3498
|
{
|
|
3445
3499
|
name: "Select",
|
|
3446
3500
|
description:
|
|
3447
|
-
'Themed picker for ONE value of a known list, or several with `multiple`. `<Select label="Status" value={status} onValueChange={setStatus} options={[{ value: "open", label: "Open" }, { value: "done", label: "Done" }]} placeholder="Choose a status" />` —
|
|
3501
|
+
'Themed picker for ONE value of a known list, or several with `multiple`. `<Select label="Status" value={status} onValueChange={setStatus} options={[{ value: "open", label: "Open" }, { value: "done", label: "Done" }]} placeholder="Choose a status" />` — the default way to let the user pick ONE of a known list. The trigger is the app\'s input field (Design > Components > Inputs) showing the chosen label; pressing it opens the options at SCREEN level, in a sheet painted from Design > Components > Dropdown menus, so no card or scroll container can clip them, and a long list scrolls inside the sheet. The trigger is a button that opens a dialog (expanded state included) and each option carries the option role and its selected state on both hosts; the list around them is a listbox on the web only. On the web Enter, Space or an arrow key opens it, the arrow keys, Home and End move between options, Enter or Space chooses, Escape closes, and focus returns to the trigger; on Android the back button closes it. Props: `options` (`[{ value, label, disabled?, testID? }]`, `value` a string or number, unique), `value`, `onValueChange(value, option)`, `placeholder` (default "Select"), `title` (the sheet\'s heading; defaults to a text `label`), `emptyText` (default "No options"), `label`, `helper`, `error`, `disabled`, `accessibilityLabel`, `closeAccessibilityLabel`, `background` / `textColor` / `borderColor` / `radius` / `height` to override one field, `style` (the trigger), `containerStyle`, `testID`; any other prop lands on the trigger. Use it rather than a dropdown of your own; when the request needs options it cannot show (colour swatches, photos with two-line captions), put a small swatch or photo picker in a `<Popover>` at its button, or a long or full-screen option list in a `<Modal>` (an emoji choice is `<EmojiPicker>`) — never an absolutely positioned list, which the widget\'s container clips on both hosts. MULTIPLE: `<Select multiple label="Tags" values={tags} onValuesChange={setTags} options={tagOptions} />` — the default way to let the user pick SEVERAL of a known list. The same trigger and sheet; each option shows a tick box and toggles, the listbox is multiselectable on the web, and the sheet\'s Done button commits the choice through `onValuesChange(values, options)` (values in listed order, called only when they changed); closing the sheet any other way (backdrop, Escape, Android back) keeps the previous choice. Space toggles the focused option on the web. The trigger shows the picked labels as small chips while they fit on its one line, otherwise "N selected" (`selectedCountText(count)` to translate it), and `placeholder` while nothing is picked; `doneText` (default "Done") names the button. In this mode `value` / `onValueChange` are ignored. A handful of options always shown inline is a `<ChipGroup selection="multiple">` of `<Chip>`s instead. SEARCHABLE: a long list the user should type to narrow. `searchable` filters the given `options` by label as they type; `onSearch(query)` hands you the trimmed query when the sheet opens (\"\") and once typing pauses (`searchDebounceMs`, default 250), and you answer with new `options` and `loading` meanwhile (`optionsError` when it failed); or pass `options` as an async function of the query and Select fetches, keeping only the latest answer. The sheet then leads with the app\'s search field (Design > Components > Search fields), shows a spinner while loading, `noResultsText` (default \"No results\") when a query finds nothing and `loadErrorText` on failure, and its rows take the Lists row height and padding when the theme sets them. It remembers the label of every option it has shown; pass `selectedOptions` for a saved value the first page may not hold, or the trigger shows the placeholder. An option may carry `leading` (an `<Avatar>`, an icon) drawn before its label. Picking people, groups or a table\'s records is `<PeoplePicker>`, which does all of this for you.',
|
|
3448
3502
|
rnComponent: null,
|
|
3449
3503
|
docsUrl: null,
|
|
3450
3504
|
},
|
|
3451
3505
|
{
|
|
3452
3506
|
name: "SearchField",
|
|
3453
3507
|
description:
|
|
3454
|
-
'Themed search box. `<SearchField value={query} onChangeText={setQuery} placeholder="Search orders" />` —
|
|
3508
|
+
'Themed search box. `<SearchField value={query} onChangeText={setQuery} placeholder="Search orders" />` — the default way to draw a field that filters or searches. It IS a `TextField` (same props, same `label` / `helper` / `error` / `disabled`, the input look restyled by Design > Components > Search fields) with a search glyph, the search key on the on-screen keyboard and a clear control that appears once there is text; clearing calls `onChangeText("")`, then `onClear` if you passed one, and keeps focus in the field. `placeholder` defaults to "Search" and `clearAccessibilityLabel` to "Clear search" — pass translated ones. `style` styles the input and `containerStyle` the box around it (use it for `flex: 1` in a toolbar). Where the glyph sits (start, end or hidden), the corner radius, background, border colour and height come from the app; `iconPosition` ("start" | "end" | "none") overrides the glyph for one field, and at the end the clear control takes the glyph\'s place while there is text. Debounce the query yourself before you fetch. Never add a magnifier of your own to it. Use it rather than a `TextInput` + an icon of your own; when the request needs a search box it cannot draw (a scope picker inside the box, a voice button beside the clear control), build your own in the input look of the theme.',
|
|
3509
|
+
rnComponent: null,
|
|
3510
|
+
docsUrl: null,
|
|
3511
|
+
},
|
|
3512
|
+
{
|
|
3513
|
+
name: "PeoplePicker",
|
|
3514
|
+
description:
|
|
3515
|
+
'Searchable picker of people, groups or records. `<PeoplePicker label="Assignee" value={assignee} onValueChange={setAssignee} />` — use it by default whenever the user picks a workspace user (an assignee, a reviewer, a recipient), a group (`source="groups"`), or a related record of a table they should search (`source="records" table={tableId}`, named by the table\'s identification column or `labelColumn`). It is a searchable `<Select>`: the same field, sheet, `label` / `helper` / `error` / `disabled`, and `multiple` with `values` / `onValuesChange`. Nothing loads until the sheet opens, each pause in typing searches on the server (never one page filtered in the widget), each user or group row leads with an `<Avatar>` (`avatarColumn` names a record\'s photo column; `showAvatars={false}` hides them), `excludeIds` leaves ids out (the current members), `groupId` limits users to one group, `filter` narrows records, and `pageSize` (default 50) sets the page. A saved value outside the first page is looked up once by id, so the trigger shows its name — `loadingText` meanwhile, `unknownText` (default "Unknown") when it is gone — never the raw id. Values are the row ids, exactly what a USER, USER_GROUP or RELATION column stores. Scopes: `directory.read:users` for users, `groups.read:*` for groups, the table\'s `datastore.read` scope for records. When the request needs what it cannot do — a people list that stays on screen with an action per row, a picker of users with presence dots or a role column — build your own with `useDirectory` / `useGroups` and the app\'s look.',
|
|
3516
|
+
rnComponent: null,
|
|
3517
|
+
docsUrl: null,
|
|
3518
|
+
},
|
|
3519
|
+
{
|
|
3520
|
+
name: "RadioGroup",
|
|
3521
|
+
description:
|
|
3522
|
+
'Themed pick-ONE of a short list that stays on screen. `<RadioGroup label="Delivery" value={speed} onValueChange={setSpeed} options={[{ value: "standard", label: "Standard" }, { value: "express", label: "Express", description: "Next working day" }]} />` — use it by default to pick one of two to about six options the user should see all at once (a plan, a delivery speed, a visibility, a size); a longer list is a `<Select>`, and filter pills are a single-choice `<ChipGroup>`. `variant` is "list" (default: the app\'s input panel, one row per option), "card" (one bordered card per option, side by side while they fit; use it when options carry a `description`) or "segmented" (the app\'s segmented tab strip, its options sharing the row evenly; for two to four short labels). List and card wear the app\'s input look (Design > Components > Inputs) and mark the pick with a round mark filled in the primary colour; segmented follows Design > Components > Tabs. Options are `{ value, label, description?, icon?, disabled?, testID? }`; `onValueChange(value, option)` fires only when a different option is picked, and values compare strictly. Props: `options`, `value`, `onValueChange`, `variant`, `label`, `helper`, `error` (a message, or `true`), `disabled`, `accessibilityLabel` (required when there is no text `label`), `style` (the group), `containerStyle` (the box around a group with a label, helper or error), `testID`; any other prop lands on the group. Every option is a 44px target with the radio role and checked state on both hosts; on the web the group is one tab stop, the arrow keys move the choice and Space picks the focused option. When the request needs what it cannot draw (an option holding a picture or a price table, or a pick laid out on a map or a grid), build your own in the app\'s look, with `accessibilityRole="radio"`, `aria-checked` and `spaceActivates`.',
|
|
3455
3523
|
rnComponent: null,
|
|
3456
3524
|
docsUrl: null,
|
|
3457
3525
|
},
|
|
@@ -3465,14 +3533,14 @@ const PRIMITIVES = [
|
|
|
3465
3533
|
{
|
|
3466
3534
|
name: "Spinner",
|
|
3467
3535
|
description:
|
|
3468
|
-
'Themed loading spinner. `<Spinner />`, `<Spinner size="lg" label="Loading orders" />` —
|
|
3536
|
+
'Themed loading spinner. `<Spinner />`, `<Spinner size="lg" label="Loading orders" />` — the default way to show that something is in progress when you cannot know its shape: a refresh, a hand-off to another app, a full-widget first load with no layout yet. A button\'s own pending action (a save, an upload, "load more") is that button\'s `busy` prop, `<Button busy>` or `<IconButton busy>`, which draws this spinner over its kept label, rather than a `<Spinner>` placed inside it. `size` is "sm" | "md" (default) | "lg" or a number; `label` draws a caption under the ring and is its accessibility label (unset, the app\'s own "Loading…"). Its colour is the app\'s one spinner colour (`theme.colors.loader`, set under Design → Loading Indicator and re-derived for whatever surface the widget sits on), so pass `color` ONLY inside a control you fill yourself, e.g. `color={theme.colors.onPrimary}` inside a button of your own on a primary fill. It stops turning when the device asks for reduced motion and is a labelled element in the busy state. Use it rather than `ActivityIndicator` or a spinner you draw; an indicator it cannot draw (a branded animation the maker described) paints `theme.colors.loader` and carries the busy state.',
|
|
3469
3537
|
rnComponent: null,
|
|
3470
3538
|
docsUrl: null,
|
|
3471
3539
|
},
|
|
3472
3540
|
{
|
|
3473
3541
|
name: "Skeleton",
|
|
3474
3542
|
description:
|
|
3475
|
-
'Themed loading placeholder. `<Skeleton shape="text" width="60%" />`, `<Skeleton shape="text" lines={3} />`, `<Skeleton shape="block" height={160} />`, `<Skeleton shape="circle" size={40} />`, and the list preset `<Skeleton shape="listRow" count={4} />` (pass `leading={false}` for rows with no avatar or icon).
|
|
3543
|
+
'Themed loading placeholder. `<Skeleton shape="text" width="60%" />`, `<Skeleton shape="text" lines={3} />`, `<Skeleton shape="block" height={160} />`, `<Skeleton shape="circle" size={40} />`, and the list preset `<Skeleton shape="listRow" count={4} />` (pass `leading={false}` for rows with no avatar or icon). The default loading state for content whose layout you already know — a list, a card, a profile, an article: draw the shape of what is coming so nothing jumps when it arrives. To mirror a custom layout, wrap bones in one group and lay them out yourself: `<Skeleton style={{ flexDirection: "row", gap: 12 }}><Skeleton shape="circle" /><Skeleton shape="text" lines={2} style={{ flex: 1 }} /></Skeleton>` — the outermost Skeleton is the ONE loading region (a `status` region with `aria-busy` on the web, one labelled accessible element in the busy state on native) and every bone inside is hidden from assistive technology. Its colour and corner radius come from the theme (Design → Components → Loading); `color` and `radius` override one skeleton. The outermost Skeleton pulses as a whole, and stops under reduced motion. Prefer it to placeholder bars of your own from `View`, which miss the Loading look of the theme and the busy state.',
|
|
3476
3544
|
rnComponent: null,
|
|
3477
3545
|
docsUrl: null,
|
|
3478
3546
|
},
|
|
@@ -3480,7 +3548,29 @@ const PRIMITIVES = [
|
|
|
3480
3548
|
{
|
|
3481
3549
|
name: "FileUpload",
|
|
3482
3550
|
description:
|
|
3483
|
-
'Themed file chooser. `<FileUpload label="Attachments" accept="image/*,.pdf" multiple files={files} onFilesChange={setFiles} />` —
|
|
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`.',
|
|
3552
|
+
rnComponent: null,
|
|
3553
|
+
docsUrl: null,
|
|
3554
|
+
},
|
|
3555
|
+
// Anchored actions, content and labels, so widgets stop layering their own.
|
|
3556
|
+
{
|
|
3557
|
+
name: "Menu",
|
|
3558
|
+
description:
|
|
3559
|
+
'Themed action menu. `<Menu trigger={<IconButton icon="more-horizontal" accessibilityLabel={t("more", "More actions")} />} accessibilityLabel={t("orderActions", "Order actions")} items={[{ label: t("edit", "Edit"), icon: "pencil", onPress: () => startEdit(order) }, canDelete && { label: t("delete", "Delete"), icon: "trash-2", danger: true, onPress: () => setPending(order) }]} />`. Use it by default for a set of actions opened from one button — a row\'s "…" button, a toolbar\'s overflow. It opens beside the trigger on a wide screen and as a bottom sheet on a phone, is drawn over the screen so no card or list clips it, closes on the backdrop press, the Escape key on web and the Android back button, hands focus back to the trigger, and is a real menu to a screen reader (on web the arrow keys, Home and End move between items and Space picks one). Picking an item closes the menu, then runs its `onPress` once the menu has left the screen, so a `<ConfirmDialog>` it opens shows on every host and hands focus back to the trigger. Item fields: `label` (required), `onPress`, `icon` (a Lucide icon name or an element), `danger` (drawn in the danger colour; confirm a destructive action with `<ConfirmDialog>`), `disabled` (dimmed and cannot be picked), `testID`, `accessibilityLabel`; a falsy item is skipped, so `cond && { … }` works. Other props: `trigger` (ONE pressable element; its own `onPress` still runs first), `accessibilityLabel`, `placement` ("bottom" default | "top"; it flips at the screen edge), `closeAccessibilityLabel` and `testID` (the item list takes `-items`). Picking one VALUE for a field is `<Select>`, not a menu. Its panel follows the theme (Design > Components > Dropdown menus) — do not re-style it. When the request needs what a menu cannot do — an item holding its own control (a switch, a stepper), a nested submenu, or a grid of choices — build your own in the app\'s look, or put it in a `<Popover>`.',
|
|
3560
|
+
rnComponent: null,
|
|
3561
|
+
docsUrl: null,
|
|
3562
|
+
},
|
|
3563
|
+
{
|
|
3564
|
+
name: "Popover",
|
|
3565
|
+
description:
|
|
3566
|
+
'Themed popover: free content beside a trigger. `<Popover trigger={<Button variant="secondary" title={t("filters", "Filters")} />} accessibilityLabel={t("filters", "Filters")}>{({ close }) => <View style={{ gap: theme.spacing.sm }}>…<Button title={t("apply", "Apply")} onPress={() => { apply(); close(); }} /></View>}</Popover>`. Use it by default for a small panel that belongs to one control and should open next to it — quick filters, a small colour-swatch or photo picker that opens at its button, a short detail card (an emoji choice is `<EmojiPicker>`; a long or full-screen option list is a `<Modal>`). It opens beside the trigger on a wide screen and as a bottom sheet on a phone, is drawn over the screen so no card or list clips it, and closes on the backdrop press, the Escape key on web and the Android back button. `children` is the content, or a function given `{ close }`. To open or close it from your own code, pass `visible` with `onVisibleChange`. Other props: `trigger` (ONE pressable element; its own `onPress` still runs first), `placement` ("bottom" default | "top"; it flips at the screen edge), `accessibilityLabel`, `closeAccessibilityLabel`, `testID`. A list of actions is `<Menu>`; a form with a title and Save / Cancel is `<Modal>`. Its panel follows the theme (Design > Components > Dropdown menus), and components inside it read a theme inked for that panel. When the request needs what a popover cannot do — a panel that stays open while the user works elsewhere on the page, or one pinned to a spot that is not a pressable control — build your own in the app\'s look.',
|
|
3567
|
+
rnComponent: null,
|
|
3568
|
+
docsUrl: null,
|
|
3569
|
+
},
|
|
3570
|
+
{
|
|
3571
|
+
name: "Tooltip",
|
|
3572
|
+
description:
|
|
3573
|
+
'Themed tooltip: a short label for the control it wraps. `<Tooltip label={t("delete", "Delete")}><IconButton icon="trash-2" accessibilityLabel={t("delete", "Delete")} onPress={remove} /></Tooltip>`. Use it by default to name an icon-only button for sighted users. On web it shows on hover and on keyboard focus, and hides on leave, blur and Escape. A touch screen has no hover, so there a long press shows it instead (a bottom sheet on a phone). Never put information ONLY in a tooltip, and keep the control\'s own `accessibilityLabel`. Wrap ONE pressable element. The tooltip puts a `View` round it, so give layout styles such as `flex` to the tooltip\'s parent, not to the child. A child with its own `onLongPress` keeps it and gets no long-press tooltip. Props: `label` (one short phrase), `placement` ("top" default | "bottom"; it flips at the screen edge), `testID`. It follows the theme (Design > Components > Dropdown menus). When the request needs what a tooltip cannot do — a link, a button or rich content inside it — use a `<Popover>`, or build your own.',
|
|
3484
3574
|
rnComponent: null,
|
|
3485
3575
|
docsUrl: null,
|
|
3486
3576
|
},
|
|
@@ -4495,7 +4585,7 @@ const VETTED_IMPORTS = [
|
|
|
4495
4585
|
platforms: ["web", "native"],
|
|
4496
4586
|
category: "drawing",
|
|
4497
4587
|
description:
|
|
4498
|
-
"SVG path generators — `line`, `area`, `arc`, `pie`, `curve*`. Each returns a path string you hand to the vetted react-native-svg's `<Path d={…} />`, so a
|
|
4588
|
+
"SVG path generators — `line`, `area`, `arc`, `pie`, `curve*`. Each returns a path string you hand to the vetted react-native-svg's `<Path d={…} />`, so a chart `<Chart>` does not draw renders identically in the Player and the Expo export from ONE source file. Pure JS.",
|
|
4499
4589
|
},
|
|
4500
4590
|
{
|
|
4501
4591
|
specifier: "d3-array",
|
|
@@ -6161,7 +6251,14 @@ const CONTRACT = deepFreeze({
|
|
|
6161
6251
|
// scrolls the page to an element inside the calling widget on both hosts.
|
|
6162
6252
|
// 1.199.0: additive (sc-11952) — an `AgentActivity` read entry may carry
|
|
6163
6253
|
// `search` and `filters`: what the agent searched for, never its rows.
|
|
6164
|
-
|
|
6254
|
+
// 1.200.0: additive (sc-12065, sc-12066, sc-12067, sc-12068) — new
|
|
6255
|
+
// `<Menu>`, `<Popover>`, `<Tooltip>` (popover scope gains radius,
|
|
6256
|
+
// shadow), `<IconButton>`, Button ghost/link/size/busy/selected and
|
|
6257
|
+
// trigger pass-through, `<FileImage>`, `Avatar fileId`, `<RadioGroup>`.
|
|
6258
|
+
// 1.201.0: additive (sc-12070, sc-12071, sc-12072) — new `<Grid>`,
|
|
6259
|
+
// `<Chart>` + `chart` theme scope + `chartColors`, `<PeoplePicker>`,
|
|
6260
|
+
// searchable `<Select>`; `useGroups` takes `ids`.
|
|
6261
|
+
version: "1.201.0",
|
|
6165
6262
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
6166
6263
|
hooks: HOOKS,
|
|
6167
6264
|
primitives: PRIMITIVES,
|