@colixsystems/widget-sdk 0.236.0 → 0.237.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 +10 -4
- package/dist/contract.cjs +38 -14
- package/dist/contract.js +38 -14
- package/dist/host.d.ts +6 -0
- package/dist/host.js +2 -0
- package/dist/index.d.ts +13 -3
- package/dist/search-field-tokens.js +85 -0
- package/dist/search-field-view.js +39 -26
- package/package.json +1 -1
- package/src/contract.js +38 -14
package/README.md
CHANGED
|
@@ -84,7 +84,13 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
|
|
|
84
84
|
|
|
85
85
|
## Status
|
|
86
86
|
|
|
87
|
-
`v0.
|
|
87
|
+
`v0.237.0` — pre-publish. The package surface (types, function names, export paths) is the v1 contract; runtime behaviour for some hooks is stubbed (each hook documents what's wired and what isn't). It is **not yet published to npm**.
|
|
88
|
+
|
|
89
|
+
### What's new in 0.237.0 (contract 1.191.0)
|
|
90
|
+
|
|
91
|
+
**Search boxes have a look of their own.** A new `search` scope on `CONTRACT.themeComponents` (Design > Components > Search fields) restyles every `<SearchField>`: `iconPosition` (`"start"` | `"end"` | `"none"`), `radius` (0–48, so a pill), `background`, `borderColor` and `height`. Each token falls back to the input look (`muted`, Design > Components > Inputs) when unset, so with none set every search box looks exactly as before. `<SearchField iconPosition>` overrides the theme for one field; any `TextField` look prop you pass still wins over the theme. At the end, the clear control takes the icon's place while the field has text, so the two never overlap. Leave the look to the theme: do not pass your own radius, background, border or height, and never add a magnifier icon yourself. `searchScopeDefaults(theme)` on `@colixsystems/widget-sdk/host` gives the Studio the input look each unset token keeps.
|
|
92
|
+
|
|
93
|
+
**Clearer section names.** Design > Components now names the scopes for what they restyle: `muted` is **Inputs**, `popover` **Dropdown menus**, `destructive` **Danger buttons**, `ring` **Button outline** (a permanent border on the Button widget, not a focus style) and `accent` **Labels**. Only the labels changed; the scope keys are the same.
|
|
88
94
|
|
|
89
95
|
### What's new in 0.236.0 (contract 1.190.0)
|
|
90
96
|
|
|
@@ -1190,7 +1196,7 @@ A binding may now carry an opacity: `"theme:colors.primary/20"` is the primary b
|
|
|
1190
1196
|
```
|
|
1191
1197
|
|
|
1192
1198
|
- **`<Card>`** takes the app's card look (Theme Settings > Cards) with no props. Override one card with `background`, `borderColor`, `radius`, `padding`, `shadow` or `gradient`, or pass `style={props.style}` when your widget declares the `card*` style fields. `as={Pressable}` makes the whole card pressable.
|
|
1193
|
-
- **`<Button>`** follows
|
|
1199
|
+
- **`<Button>`** follows Design > Components > Buttons (and Danger buttons / Button outline). `variant` is `primary` | `secondary` | `outlined` | `danger`; `icon` / `iconPosition` add a lucide glyph; omit `onPress` for a visual inside something pressable; `fields` overrides one button's surface.
|
|
1194
1200
|
- **`<TextField>`** is a `TextInput` in the app's input look (Theme Settings > Muted, which gains a **`height`** token); `multiline` grows to a ceiling, then scrolls.
|
|
1195
1201
|
|
|
1196
1202
|
The card and button resolvers (`cardOverlay`, `shadowStyle`, `marginStyle`, `buttonSurfaceOverlay`, …) are exported too. Additive: the built-in widgets render exactly as before.
|
|
@@ -2920,11 +2926,11 @@ import { defineWidget, validateManifest, useDatastoreQuery, Text, View } from "@
|
|
|
2920
2926
|
- `isSafeMarkdownImageSrc`, `isHttpImageSrc`, `normaliseMarkdownImageSize`, `readMarkdownAlt` — the image half of that grammar, for a widget rendering images itself via `renderImage`. `isSafeMarkdownImageSrc` is the same allowlist the parser applies (an `http(s)` URL or a filestore id; a `javascript:` scheme is refused), so the widget and the parser cannot disagree about which src is renderable.
|
|
2921
2927
|
- `List`, `ListRow`, `ListHeader`, `EmptyState` — the themed list building blocks. `<ListRow title subtitle leading trailing onPress selected divider>` draws one record row from the theme's `list` scope (padding, height, gap, radius, background, divider); `<List>` spaces its rows and hands overrides down; `<ListHeader>` labels a table's columns on the rows' own inset; `<EmptyState icon title message>` is the "nothing here" panel. Never hand-roll a row or a header row from `View` + `Pressable`.
|
|
2922
2928
|
- `Badge`, `Avatar`, `Progress`, `Stat`, `Divider` — the themed status pill, person picture, completion bar or ring, headline figure and rule the built-in widgets of the same names render through. Reach for them inside a record row instead of hand-rolling a pill or a circle from `View`.
|
|
2923
|
-
- `Card`, `Button`, `TextField` — the themed card surface, button and text field the built-in widgets render through. With no props each takes the app's look from
|
|
2929
|
+
- `Card`, `Button`, `TextField` — the themed card surface, button and text field the built-in widgets render through. With no props each takes the app's look from Design > Components (Cards, Buttons, Inputs); never hand-roll one from `View` / `Pressable` / a bare `TextInput`.
|
|
2924
2930
|
- `Banner` — the themed inline message. `<Banner tone message>` shows an error, warning, success or notice in place, styled by the theme's `alert` scope; `action` adds one button and `onDismiss` a close control. Use `useToast()` for a brief confirmation instead. Never hand-roll a tinted error box.
|
|
2925
2931
|
- `Accordion`, `AccordionItem` — themed collapsible sections. `<Accordion><AccordionItem title subtitle trailing>…</AccordionItem></Accordion>` draws its surface from the theme's `card` scope and its headers and dividers from the `list` scope; one section open at a time, or `multiple`. Never hand-roll a collapsible from `Pressable` + a chevron.
|
|
2926
2932
|
- `Spinner`, `Skeleton` — the themed loading states. `<Spinner size label>` for a pending action; `<Skeleton shape="text" | "block" | "circle" | "listRow">` (or a group of bones) for content whose layout you know. The spinner wears `colors.loader`, the skeleton follows the theme's `loading` scope; both stop under reduced motion and mark one busy region. Never use `ActivityIndicator` or hand-build placeholder bars.
|
|
2927
|
-
- `Checkbox`, `Switch`, `Select`, `SearchField` — the themed tick box, on/off toggle, single-choice picker and search box. Like `TextField` they follow the theme's `muted` scope (and `popover
|
|
2933
|
+
- `Checkbox`, `Switch`, `Select`, `SearchField` — the themed tick box, on/off toggle, single-choice picker and search box. Like `TextField` they follow the theme's `muted` scope, Design > Components > Inputs (and `popover`, Dropdown menus, for the Select's option sheet; `SearchField` also reads `search`, Search fields, for its glyph position, corner radius, background, border colour and height, each falling back to Inputs) and take `label`, `helper`, `error` and `disabled`. `Select` opens its options at screen level. Never hand-roll one from `Pressable` and `View`.
|
|
2928
2934
|
- `Tabs` — the themed tab strip. `<Tabs items value onChange>` draws a segmented control, pills or an underline as the theme's `tabs` scope says; render the selected panel yourself. Never hand-roll tabs from `Pressable` chips.
|
|
2929
2935
|
- `Chip`, `ChipGroup` — the themed filter chip, tag or pill. `<Chip label selected onPress onRemove disabled>` follows the theme's `chip` scope; `<ChipGroup>` spaces and wraps its chips and, with `selection="single"`, announces them as one choice. Never hand-roll a chip from `Pressable` + a pill radius.
|
|
2930
2936
|
- `textRoleStyle(role, typography, colors)`, `TEXT_ROLES` — the style of a semantic text tier, shared with the built-in widgets.
|
package/dist/contract.cjs
CHANGED
|
@@ -275,6 +275,9 @@ function formatThemeColorTokenRef(path, percent) {
|
|
|
275
275
|
// The looks a `tabs.style` token may name; `<Tabs>` draws each.
|
|
276
276
|
const THEME_TAB_STYLES = Object.freeze(["segmented", "pill", "underline"]);
|
|
277
277
|
|
|
278
|
+
// Where `<SearchField>` draws its magnifier; "start" is the look it always had.
|
|
279
|
+
const THEME_SEARCH_ICON_POSITIONS = Object.freeze(["start", "end", "none"]);
|
|
280
|
+
|
|
278
281
|
const THEME_COMPONENT_SHADOWS = Object.freeze(["none", "sm", "md", "lg"]);
|
|
279
282
|
|
|
280
283
|
// sc-3857 — the `textTransform` token's closed enum, matching the per-instance
|
|
@@ -585,7 +588,7 @@ const THEME_COMPONENTS = Object.freeze({
|
|
|
585
588
|
// than its `color` field, which the `text` scope already owns -- two
|
|
586
589
|
// scopes writing the same field on the same widget would race.
|
|
587
590
|
accent: Object.freeze({
|
|
588
|
-
label: "
|
|
591
|
+
label: "Labels",
|
|
589
592
|
tokens: Object.freeze({
|
|
590
593
|
background: Object.freeze({ type: "color", uiDefault: "colors.primarySoft" }),
|
|
591
594
|
borderColor: Object.freeze({ type: "color", uiDefault: "colors.primary" }),
|
|
@@ -602,7 +605,7 @@ const THEME_COMPONENTS = Object.freeze({
|
|
|
602
605
|
// sc-5392 -- the Button widget's danger/delete variant. Separate token set
|
|
603
606
|
// from `button` above so "make the buttons coral" never recolours Delete.
|
|
604
607
|
destructive: Object.freeze({
|
|
605
|
-
label: "
|
|
608
|
+
label: "Danger buttons",
|
|
606
609
|
tokens: Object.freeze({
|
|
607
610
|
background: Object.freeze({ type: "color", uiDefault: "colors.danger" }),
|
|
608
611
|
textColor: Object.freeze({ type: "color", uiDefault: "colors.onPrimary" }),
|
|
@@ -617,7 +620,7 @@ const THEME_COMPONENTS = Object.freeze({
|
|
|
617
620
|
// scope) rather than to Text/Label/Data Value, whose `color` field the
|
|
618
621
|
// `text` scope already owns.
|
|
619
622
|
muted: Object.freeze({
|
|
620
|
-
label: "
|
|
623
|
+
label: "Inputs",
|
|
621
624
|
// The SDK's form controls read this scope directly, in every widget.
|
|
622
625
|
sdkComponents: Object.freeze(["TextField", "Checkbox", "Switch", "Select", "SearchField"]),
|
|
623
626
|
tokens: Object.freeze({
|
|
@@ -636,7 +639,7 @@ const THEME_COMPONENTS = Object.freeze({
|
|
|
636
639
|
// sc-5392 -- dropdown/menu surfaces. The form widgets' choice-field option
|
|
637
640
|
// list is the closest thing the built-in catalog has to a popover panel.
|
|
638
641
|
popover: Object.freeze({
|
|
639
|
-
label: "
|
|
642
|
+
label: "Dropdown menus",
|
|
640
643
|
sdkComponents: Object.freeze(["Select"]),
|
|
641
644
|
tokens: Object.freeze({
|
|
642
645
|
background: Object.freeze({ type: "color", uiDefault: "colors.surface" }),
|
|
@@ -648,11 +651,26 @@ const THEME_COMPONENTS = Object.freeze({
|
|
|
648
651
|
"appstudio.form-builder": POPOVER_FIELDS,
|
|
649
652
|
}),
|
|
650
653
|
}),
|
|
651
|
-
//
|
|
654
|
+
// Search boxes: `<SearchField>` reads this scope directly. Every token is
|
|
655
|
+
// unset by default and falls back to the `muted` field it draws.
|
|
656
|
+
search: Object.freeze({
|
|
657
|
+
label: "Search fields",
|
|
658
|
+
sdkComponents: Object.freeze(["SearchField"]),
|
|
659
|
+
tokens: Object.freeze({
|
|
660
|
+
iconPosition: Object.freeze({ type: "enum", values: THEME_SEARCH_ICON_POSITIONS }),
|
|
661
|
+
// 48 is past half the tallest field, so its ceiling draws a pill.
|
|
662
|
+
radius: Object.freeze({ type: "size", min: 0, max: 48 }),
|
|
663
|
+
background: Object.freeze({ type: "color" }),
|
|
664
|
+
borderColor: Object.freeze({ type: "color" }),
|
|
665
|
+
height: Object.freeze({ type: "size", min: 32, max: 72 }),
|
|
666
|
+
}),
|
|
667
|
+
targets: Object.freeze({}),
|
|
668
|
+
}),
|
|
669
|
+
// sc-5392 -- not a keyboard focus style: a permanent emphasis border
|
|
652
670
|
// on the Button widget, applied after its variant colours and beaten by the
|
|
653
671
|
// widget's own per-instance `borderColor` (REQ-THEME-13 stays final).
|
|
654
672
|
ring: Object.freeze({
|
|
655
|
-
label: "
|
|
673
|
+
label: "Button outline",
|
|
656
674
|
tokens: Object.freeze({
|
|
657
675
|
color: Object.freeze({ type: "color", uiDefault: "colors.primary" }),
|
|
658
676
|
width: Object.freeze({ type: "size", min: 0, max: 8 }),
|
|
@@ -3093,7 +3111,7 @@ const PRIMITIVES = [
|
|
|
3093
3111
|
{
|
|
3094
3112
|
name: "Switch",
|
|
3095
3113
|
description:
|
|
3096
|
-
'Themed on/off toggle. `<Switch label="Email me updates" value={on} onValueChange={setOn} />` — THE 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 >
|
|
3114
|
+
'Themed on/off toggle. `<Switch label="Email me updates" value={on} onValueChange={setOn} />` — THE 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. Never hand-roll a toggle from `Pressable` + a sliding `View`.',
|
|
3097
3115
|
rnComponent: "Switch",
|
|
3098
3116
|
docsUrl: "https://reactnative.dev/docs/switch",
|
|
3099
3117
|
},
|
|
@@ -3202,7 +3220,7 @@ const PRIMITIVES = [
|
|
|
3202
3220
|
{
|
|
3203
3221
|
name: "ConfirmDialog",
|
|
3204
3222
|
description:
|
|
3205
|
-
'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 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. Never hand-build one from a `<Modal>` or an `<Overlay>` with your own two buttons, 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
|
|
3223
|
+
'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 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. Never hand-build one from a `<Modal>` or an `<Overlay>` with your own two buttons, 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.',
|
|
3206
3224
|
rnComponent: null,
|
|
3207
3225
|
docsUrl: null,
|
|
3208
3226
|
},
|
|
@@ -3349,35 +3367,35 @@ const PRIMITIVES = [
|
|
|
3349
3367
|
{
|
|
3350
3368
|
name: "Button",
|
|
3351
3369
|
description:
|
|
3352
|
-
'Themed button. `<Button title="Save" onPress={save} />` — THE way to draw a button: its fill, label colour, border, corner radius, padding, weight, shadow and gradient come from the app\'s button look (
|
|
3370
|
+
'Themed button. `<Button title="Save" onPress={save} />` — THE way to draw 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"), `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. 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. Never hand-roll a button from `Pressable` + background + radius.',
|
|
3353
3371
|
rnComponent: null,
|
|
3354
3372
|
docsUrl: null,
|
|
3355
3373
|
},
|
|
3356
3374
|
{
|
|
3357
3375
|
name: "TextField",
|
|
3358
3376
|
description:
|
|
3359
|
-
'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 (
|
|
3377
|
+
'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`.',
|
|
3360
3378
|
rnComponent: null,
|
|
3361
3379
|
docsUrl: null,
|
|
3362
3380
|
},
|
|
3363
3381
|
{
|
|
3364
3382
|
name: "Checkbox",
|
|
3365
3383
|
description:
|
|
3366
|
-
'Themed tick box. `<Checkbox label="Remember me" value={checked} onValueChange={setChecked} />` — THE 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 >
|
|
3384
|
+
'Themed tick box. `<Checkbox label="Remember me" value={checked} onValueChange={setChecked} />` — THE 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. Never hand-roll a tick box from `Pressable` + a bordered `View` + a "✓".',
|
|
3367
3385
|
rnComponent: null,
|
|
3368
3386
|
docsUrl: null,
|
|
3369
3387
|
},
|
|
3370
3388
|
{
|
|
3371
3389
|
name: "Select",
|
|
3372
3390
|
description:
|
|
3373
|
-
'Themed single-choice picker. `<Select label="Status" value={status} onValueChange={setStatus} options={[{ value: "open", label: "Open" }, { value: "done", label: "Done" }]} placeholder="Choose a status" />` — THE way to let the user pick ONE of a known list. The trigger is the app\'s input field (Design > Components >
|
|
3391
|
+
'Themed single-choice picker. `<Select label="Status" value={status} onValueChange={setStatus} options={[{ value: "open", label: "Open" }, { value: "done", label: "Done" }]} placeholder="Choose a status" />` — THE 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. Never hand-roll a dropdown from `Pressable` + an absolutely positioned list: it is clipped by the widget\'s container on both hosts.',
|
|
3374
3392
|
rnComponent: null,
|
|
3375
3393
|
docsUrl: null,
|
|
3376
3394
|
},
|
|
3377
3395
|
{
|
|
3378
3396
|
name: "SearchField",
|
|
3379
3397
|
description:
|
|
3380
|
-
'Themed search box. `<SearchField value={query} onChangeText={setQuery} placeholder="Search orders" />` — THE way to draw a field that filters or searches. It IS a `TextField` (same
|
|
3398
|
+
'Themed search box. `<SearchField value={query} onChangeText={setQuery} placeholder="Search orders" />` — THE 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 hand-roll a search box from a `TextInput` + an icon, and never add a magnifier of your own.',
|
|
3381
3399
|
rnComponent: null,
|
|
3382
3400
|
docsUrl: null,
|
|
3383
3401
|
},
|
|
@@ -6053,7 +6071,12 @@ const CONTRACT = deepFreeze({
|
|
|
6053
6071
|
// calendar sheet instead of the browser's date popup; both hosts show
|
|
6054
6072
|
// the value in the app's language with a clear button. New optional
|
|
6055
6073
|
// props: placeholder, clearLabel, doneLabel, closeAccessibilityLabel, testID.
|
|
6056
|
-
|
|
6074
|
+
// 1.191.0: additive (sc-11759) — `themeComponents.search` styles every
|
|
6075
|
+
// `<SearchField>`: icon position, corner radius, background, border
|
|
6076
|
+
// colour, height, each falling back to the `muted` field. The field
|
|
6077
|
+
// takes an `iconPosition` prop. Scope labels renamed (sc-11763); keys
|
|
6078
|
+
// unchanged.
|
|
6079
|
+
version: "1.191.0",
|
|
6057
6080
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
6058
6081
|
hooks: HOOKS,
|
|
6059
6082
|
primitives: PRIMITIVES,
|
|
@@ -6069,6 +6092,7 @@ const CONTRACT = deepFreeze({
|
|
|
6069
6092
|
themeColorTokens: THEME_COLOR_TOKENS,
|
|
6070
6093
|
themeComponents: THEME_COMPONENTS,
|
|
6071
6094
|
themeComponentTabStyles: THEME_TAB_STYLES,
|
|
6095
|
+
themeComponentSearchIconPositions: THEME_SEARCH_ICON_POSITIONS,
|
|
6072
6096
|
themeComponentShadows: THEME_COMPONENT_SHADOWS,
|
|
6073
6097
|
themeComponentTextTransforms: THEME_COMPONENT_TEXT_TRANSFORMS,
|
|
6074
6098
|
themeComponentGradient: THEME_COMPONENT_GRADIENT,
|
package/dist/contract.js
CHANGED
|
@@ -275,6 +275,9 @@ function formatThemeColorTokenRef(path, percent) {
|
|
|
275
275
|
// The looks a `tabs.style` token may name; `<Tabs>` draws each.
|
|
276
276
|
const THEME_TAB_STYLES = Object.freeze(["segmented", "pill", "underline"]);
|
|
277
277
|
|
|
278
|
+
// Where `<SearchField>` draws its magnifier; "start" is the look it always had.
|
|
279
|
+
const THEME_SEARCH_ICON_POSITIONS = Object.freeze(["start", "end", "none"]);
|
|
280
|
+
|
|
278
281
|
const THEME_COMPONENT_SHADOWS = Object.freeze(["none", "sm", "md", "lg"]);
|
|
279
282
|
|
|
280
283
|
// sc-3857 — the `textTransform` token's closed enum, matching the per-instance
|
|
@@ -585,7 +588,7 @@ const THEME_COMPONENTS = Object.freeze({
|
|
|
585
588
|
// than its `color` field, which the `text` scope already owns -- two
|
|
586
589
|
// scopes writing the same field on the same widget would race.
|
|
587
590
|
accent: Object.freeze({
|
|
588
|
-
label: "
|
|
591
|
+
label: "Labels",
|
|
589
592
|
tokens: Object.freeze({
|
|
590
593
|
background: Object.freeze({ type: "color", uiDefault: "colors.primarySoft" }),
|
|
591
594
|
borderColor: Object.freeze({ type: "color", uiDefault: "colors.primary" }),
|
|
@@ -602,7 +605,7 @@ const THEME_COMPONENTS = Object.freeze({
|
|
|
602
605
|
// sc-5392 -- the Button widget's danger/delete variant. Separate token set
|
|
603
606
|
// from `button` above so "make the buttons coral" never recolours Delete.
|
|
604
607
|
destructive: Object.freeze({
|
|
605
|
-
label: "
|
|
608
|
+
label: "Danger buttons",
|
|
606
609
|
tokens: Object.freeze({
|
|
607
610
|
background: Object.freeze({ type: "color", uiDefault: "colors.danger" }),
|
|
608
611
|
textColor: Object.freeze({ type: "color", uiDefault: "colors.onPrimary" }),
|
|
@@ -617,7 +620,7 @@ const THEME_COMPONENTS = Object.freeze({
|
|
|
617
620
|
// scope) rather than to Text/Label/Data Value, whose `color` field the
|
|
618
621
|
// `text` scope already owns.
|
|
619
622
|
muted: Object.freeze({
|
|
620
|
-
label: "
|
|
623
|
+
label: "Inputs",
|
|
621
624
|
// The SDK's form controls read this scope directly, in every widget.
|
|
622
625
|
sdkComponents: Object.freeze(["TextField", "Checkbox", "Switch", "Select", "SearchField"]),
|
|
623
626
|
tokens: Object.freeze({
|
|
@@ -636,7 +639,7 @@ const THEME_COMPONENTS = Object.freeze({
|
|
|
636
639
|
// sc-5392 -- dropdown/menu surfaces. The form widgets' choice-field option
|
|
637
640
|
// list is the closest thing the built-in catalog has to a popover panel.
|
|
638
641
|
popover: Object.freeze({
|
|
639
|
-
label: "
|
|
642
|
+
label: "Dropdown menus",
|
|
640
643
|
sdkComponents: Object.freeze(["Select"]),
|
|
641
644
|
tokens: Object.freeze({
|
|
642
645
|
background: Object.freeze({ type: "color", uiDefault: "colors.surface" }),
|
|
@@ -648,11 +651,26 @@ const THEME_COMPONENTS = Object.freeze({
|
|
|
648
651
|
"appstudio.form-builder": POPOVER_FIELDS,
|
|
649
652
|
}),
|
|
650
653
|
}),
|
|
651
|
-
//
|
|
654
|
+
// Search boxes: `<SearchField>` reads this scope directly. Every token is
|
|
655
|
+
// unset by default and falls back to the `muted` field it draws.
|
|
656
|
+
search: Object.freeze({
|
|
657
|
+
label: "Search fields",
|
|
658
|
+
sdkComponents: Object.freeze(["SearchField"]),
|
|
659
|
+
tokens: Object.freeze({
|
|
660
|
+
iconPosition: Object.freeze({ type: "enum", values: THEME_SEARCH_ICON_POSITIONS }),
|
|
661
|
+
// 48 is past half the tallest field, so its ceiling draws a pill.
|
|
662
|
+
radius: Object.freeze({ type: "size", min: 0, max: 48 }),
|
|
663
|
+
background: Object.freeze({ type: "color" }),
|
|
664
|
+
borderColor: Object.freeze({ type: "color" }),
|
|
665
|
+
height: Object.freeze({ type: "size", min: 32, max: 72 }),
|
|
666
|
+
}),
|
|
667
|
+
targets: Object.freeze({}),
|
|
668
|
+
}),
|
|
669
|
+
// sc-5392 -- not a keyboard focus style: a permanent emphasis border
|
|
652
670
|
// on the Button widget, applied after its variant colours and beaten by the
|
|
653
671
|
// widget's own per-instance `borderColor` (REQ-THEME-13 stays final).
|
|
654
672
|
ring: Object.freeze({
|
|
655
|
-
label: "
|
|
673
|
+
label: "Button outline",
|
|
656
674
|
tokens: Object.freeze({
|
|
657
675
|
color: Object.freeze({ type: "color", uiDefault: "colors.primary" }),
|
|
658
676
|
width: Object.freeze({ type: "size", min: 0, max: 8 }),
|
|
@@ -3093,7 +3111,7 @@ const PRIMITIVES = [
|
|
|
3093
3111
|
{
|
|
3094
3112
|
name: "Switch",
|
|
3095
3113
|
description:
|
|
3096
|
-
'Themed on/off toggle. `<Switch label="Email me updates" value={on} onValueChange={setOn} />` — THE 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 >
|
|
3114
|
+
'Themed on/off toggle. `<Switch label="Email me updates" value={on} onValueChange={setOn} />` — THE 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. Never hand-roll a toggle from `Pressable` + a sliding `View`.',
|
|
3097
3115
|
rnComponent: "Switch",
|
|
3098
3116
|
docsUrl: "https://reactnative.dev/docs/switch",
|
|
3099
3117
|
},
|
|
@@ -3202,7 +3220,7 @@ const PRIMITIVES = [
|
|
|
3202
3220
|
{
|
|
3203
3221
|
name: "ConfirmDialog",
|
|
3204
3222
|
description:
|
|
3205
|
-
'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 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. Never hand-build one from a `<Modal>` or an `<Overlay>` with your own two buttons, 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
|
|
3223
|
+
'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 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. Never hand-build one from a `<Modal>` or an `<Overlay>` with your own two buttons, 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.',
|
|
3206
3224
|
rnComponent: null,
|
|
3207
3225
|
docsUrl: null,
|
|
3208
3226
|
},
|
|
@@ -3349,35 +3367,35 @@ const PRIMITIVES = [
|
|
|
3349
3367
|
{
|
|
3350
3368
|
name: "Button",
|
|
3351
3369
|
description:
|
|
3352
|
-
'Themed button. `<Button title="Save" onPress={save} />` — THE way to draw a button: its fill, label colour, border, corner radius, padding, weight, shadow and gradient come from the app\'s button look (
|
|
3370
|
+
'Themed button. `<Button title="Save" onPress={save} />` — THE way to draw 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"), `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. 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. Never hand-roll a button from `Pressable` + background + radius.',
|
|
3353
3371
|
rnComponent: null,
|
|
3354
3372
|
docsUrl: null,
|
|
3355
3373
|
},
|
|
3356
3374
|
{
|
|
3357
3375
|
name: "TextField",
|
|
3358
3376
|
description:
|
|
3359
|
-
'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 (
|
|
3377
|
+
'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`.',
|
|
3360
3378
|
rnComponent: null,
|
|
3361
3379
|
docsUrl: null,
|
|
3362
3380
|
},
|
|
3363
3381
|
{
|
|
3364
3382
|
name: "Checkbox",
|
|
3365
3383
|
description:
|
|
3366
|
-
'Themed tick box. `<Checkbox label="Remember me" value={checked} onValueChange={setChecked} />` — THE 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 >
|
|
3384
|
+
'Themed tick box. `<Checkbox label="Remember me" value={checked} onValueChange={setChecked} />` — THE 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. Never hand-roll a tick box from `Pressable` + a bordered `View` + a "✓".',
|
|
3367
3385
|
rnComponent: null,
|
|
3368
3386
|
docsUrl: null,
|
|
3369
3387
|
},
|
|
3370
3388
|
{
|
|
3371
3389
|
name: "Select",
|
|
3372
3390
|
description:
|
|
3373
|
-
'Themed single-choice picker. `<Select label="Status" value={status} onValueChange={setStatus} options={[{ value: "open", label: "Open" }, { value: "done", label: "Done" }]} placeholder="Choose a status" />` — THE way to let the user pick ONE of a known list. The trigger is the app\'s input field (Design > Components >
|
|
3391
|
+
'Themed single-choice picker. `<Select label="Status" value={status} onValueChange={setStatus} options={[{ value: "open", label: "Open" }, { value: "done", label: "Done" }]} placeholder="Choose a status" />` — THE 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. Never hand-roll a dropdown from `Pressable` + an absolutely positioned list: it is clipped by the widget\'s container on both hosts.',
|
|
3374
3392
|
rnComponent: null,
|
|
3375
3393
|
docsUrl: null,
|
|
3376
3394
|
},
|
|
3377
3395
|
{
|
|
3378
3396
|
name: "SearchField",
|
|
3379
3397
|
description:
|
|
3380
|
-
'Themed search box. `<SearchField value={query} onChangeText={setQuery} placeholder="Search orders" />` — THE way to draw a field that filters or searches. It IS a `TextField` (same
|
|
3398
|
+
'Themed search box. `<SearchField value={query} onChangeText={setQuery} placeholder="Search orders" />` — THE 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 hand-roll a search box from a `TextInput` + an icon, and never add a magnifier of your own.',
|
|
3381
3399
|
rnComponent: null,
|
|
3382
3400
|
docsUrl: null,
|
|
3383
3401
|
},
|
|
@@ -6049,7 +6067,12 @@ const CONTRACT = deepFreeze({
|
|
|
6049
6067
|
// calendar sheet instead of the browser's date popup; both hosts show
|
|
6050
6068
|
// the value in the app's language with a clear button. New optional
|
|
6051
6069
|
// props: placeholder, clearLabel, doneLabel, closeAccessibilityLabel, testID.
|
|
6052
|
-
|
|
6070
|
+
// 1.191.0: additive (sc-11759) — `themeComponents.search` styles every
|
|
6071
|
+
// `<SearchField>`: icon position, corner radius, background, border
|
|
6072
|
+
// colour, height, each falling back to the `muted` field. The field
|
|
6073
|
+
// takes an `iconPosition` prop. Scope labels renamed (sc-11763); keys
|
|
6074
|
+
// unchanged.
|
|
6075
|
+
version: "1.191.0",
|
|
6053
6076
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
6054
6077
|
hooks: HOOKS,
|
|
6055
6078
|
primitives: PRIMITIVES,
|
|
@@ -6065,6 +6088,7 @@ const CONTRACT = deepFreeze({
|
|
|
6065
6088
|
themeColorTokens: THEME_COLOR_TOKENS,
|
|
6066
6089
|
themeComponents: THEME_COMPONENTS,
|
|
6067
6090
|
themeComponentTabStyles: THEME_TAB_STYLES,
|
|
6091
|
+
themeComponentSearchIconPositions: THEME_SEARCH_ICON_POSITIONS,
|
|
6068
6092
|
themeComponentShadows: THEME_COMPONENT_SHADOWS,
|
|
6069
6093
|
themeComponentTextTransforms: THEME_COMPONENT_TEXT_TRANSFORMS,
|
|
6070
6094
|
themeComponentGradient: THEME_COMPONENT_GRADIENT,
|
package/dist/host.d.ts
CHANGED
|
@@ -51,6 +51,12 @@ export function bannerScopeDefaults(theme: unknown): Record<string, string | num
|
|
|
51
51
|
*/
|
|
52
52
|
export function chipScopeDefaults(theme: unknown): Record<string, string | number>;
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* What each token of the `search` component scope resolves to while unset,
|
|
56
|
+
* keyed by token name: the input look `<SearchField>` falls back to.
|
|
57
|
+
*/
|
|
58
|
+
export function searchScopeDefaults(theme: unknown): Record<string, string | number>;
|
|
59
|
+
|
|
54
60
|
/**
|
|
55
61
|
* The theme a surface painting `backgroundColor` hands its subtree: surface
|
|
56
62
|
* tokens derived for that fill, and any type tier whose own ink cannot be read
|
package/dist/host.js
CHANGED
|
@@ -54,6 +54,8 @@ export { resolveModalTokens, modalScopeDefaults } from "./modal-tokens.js";
|
|
|
54
54
|
// What each `chip` scope token resolves to while unset, for the Studio's
|
|
55
55
|
// Design page to preview beside its controls.
|
|
56
56
|
export { chipScopeDefaults } from "./chip-tokens.js";
|
|
57
|
+
// The input look each unset `search` token falls back to, for the same page.
|
|
58
|
+
export { searchScopeDefaults } from "./search-field-tokens.js";
|
|
57
59
|
|
|
58
60
|
export {
|
|
59
61
|
createToastController,
|
package/dist/index.d.ts
CHANGED
|
@@ -3939,7 +3939,7 @@ export type ButtonVariant = "primary" | "secondary" | "outlined" | "danger";
|
|
|
3939
3939
|
export const BUTTON_VARIANTS: readonly ButtonVariant[];
|
|
3940
3940
|
|
|
3941
3941
|
/**
|
|
3942
|
-
* Themed button (
|
|
3942
|
+
* Themed button (Design > Components > Buttons, Danger buttons, Button outline). Omit
|
|
3943
3943
|
* `onPress` for a purely visual button inside something pressable.
|
|
3944
3944
|
*
|
|
3945
3945
|
* @example
|
|
@@ -4000,7 +4000,7 @@ export interface FormFieldOverrides {
|
|
|
4000
4000
|
}
|
|
4001
4001
|
|
|
4002
4002
|
/**
|
|
4003
|
-
* Themed text-entry field (Design > Components >
|
|
4003
|
+
* Themed text-entry field (Design > Components > Inputs). Every TextInput prop
|
|
4004
4004
|
* works.
|
|
4005
4005
|
*
|
|
4006
4006
|
* @example
|
|
@@ -4100,9 +4100,14 @@ export function spaceActivates(
|
|
|
4100
4100
|
onPress: () => void,
|
|
4101
4101
|
): (event: { key: string; repeat?: boolean; preventDefault: () => void }) => void;
|
|
4102
4102
|
|
|
4103
|
+
/** Where `<SearchField>` draws its search glyph. */
|
|
4104
|
+
export type SearchIconPosition = "start" | "end" | "none";
|
|
4105
|
+
|
|
4103
4106
|
/**
|
|
4104
4107
|
* Themed search box: a `TextField` with a search glyph, the search key and a
|
|
4105
|
-
* clear control. Every `TextField` prop works.
|
|
4108
|
+
* clear control. Every `TextField` prop works. It wears the input look,
|
|
4109
|
+
* restyled by the theme's `search` scope (glyph position, corner radius,
|
|
4110
|
+
* background, border colour, height); each unset token keeps the input look.
|
|
4106
4111
|
*
|
|
4107
4112
|
* @example
|
|
4108
4113
|
* <SearchField value={query} onChangeText={setQuery} placeholder="Search orders" />
|
|
@@ -4110,6 +4115,11 @@ export function spaceActivates(
|
|
|
4110
4115
|
export const SearchField: (props: FormControlProps & {
|
|
4111
4116
|
value?: string;
|
|
4112
4117
|
onChangeText?: (text: string) => void;
|
|
4118
|
+
/**
|
|
4119
|
+
* Overrides the theme's glyph position for this field. At "end" the clear
|
|
4120
|
+
* control takes the glyph's place while the field has text.
|
|
4121
|
+
*/
|
|
4122
|
+
iconPosition?: SearchIconPosition;
|
|
4113
4123
|
/** Called after the clear control empties the field. */
|
|
4114
4124
|
onClear?: () => void;
|
|
4115
4125
|
/** Default "Search". */
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// What `<SearchField>` draws from the theme's `search` scope: where its
|
|
2
|
+
// magnifier sits, and the field look it lays over `<TextField>`'s `muted` one.
|
|
3
|
+
// Platform-free, so both hosts lay the field out from the same answer.
|
|
4
|
+
|
|
5
|
+
import { CONTRACT } from "./contract.js";
|
|
6
|
+
import { normaliseThemeComponents } from "./theme-components.js";
|
|
7
|
+
import { resolveTextFieldTokens } from "./text-field-tokens.js";
|
|
8
|
+
|
|
9
|
+
export const SEARCH_ICON_POSITIONS = CONTRACT.themeComponentSearchIconPositions;
|
|
10
|
+
export const DEFAULT_SEARCH_ICON_POSITION = "start";
|
|
11
|
+
|
|
12
|
+
// The `search` tokens that restyle the field; each is a `<TextField>` override.
|
|
13
|
+
export const SEARCH_FIELD_LOOK_TOKENS = Object.freeze([
|
|
14
|
+
"background",
|
|
15
|
+
"borderColor",
|
|
16
|
+
"radius",
|
|
17
|
+
"height",
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
function searchScope(theme) {
|
|
21
|
+
const components = theme && typeof theme === "object" ? theme.components : undefined;
|
|
22
|
+
return normaliseThemeComponents({ search: components && components.search }).search || {};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The magnifier's position: the field's own prop, then the theme, then start.
|
|
27
|
+
*
|
|
28
|
+
* @param {unknown} own — the `iconPosition` prop.
|
|
29
|
+
* @param {unknown} themed — the theme's `search.iconPosition`.
|
|
30
|
+
*/
|
|
31
|
+
export function searchIconPosition(own, themed) {
|
|
32
|
+
if (SEARCH_ICON_POSITIONS.includes(own)) return own;
|
|
33
|
+
if (SEARCH_ICON_POSITIONS.includes(themed)) return themed;
|
|
34
|
+
return DEFAULT_SEARCH_ICON_POSITION;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Resolve one search field. `field` holds only the tokens the theme set, so
|
|
39
|
+
* an unset one leaves `<TextField>` on the `muted` look it always drew.
|
|
40
|
+
*
|
|
41
|
+
* @param {object} theme — the resolved workspace theme (`useTheme()` shape).
|
|
42
|
+
* @param {{ iconPosition?: string }} [own] — the field's own props.
|
|
43
|
+
* @returns {{ iconPosition: string, field: Record<string, string|number> }}
|
|
44
|
+
*/
|
|
45
|
+
export function resolveSearchFieldTokens(theme, own) {
|
|
46
|
+
const scope = searchScope(theme);
|
|
47
|
+
const field = {};
|
|
48
|
+
for (const name of SEARCH_FIELD_LOOK_TOKENS) {
|
|
49
|
+
if (scope[name] !== undefined) field[name] = scope[name];
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
iconPosition: searchIconPosition(own && own.iconPosition, scope.iconPosition),
|
|
53
|
+
field,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Where the glyph and the clear control go. At the end, the clear control
|
|
59
|
+
* takes the glyph's slot while there is text, so the two never overlap.
|
|
60
|
+
*
|
|
61
|
+
* @param {string} iconPosition — a resolved SEARCH_ICON_POSITIONS value.
|
|
62
|
+
* @param {boolean} clearable — the field has text and is enabled.
|
|
63
|
+
* @returns {{ glyph: "start"|"end"|null, padStart: boolean, padEnd: boolean }}
|
|
64
|
+
*/
|
|
65
|
+
export function searchFieldLayout(iconPosition, clearable) {
|
|
66
|
+
const glyphAtEnd = iconPosition === "end" && !clearable;
|
|
67
|
+
return {
|
|
68
|
+
glyph: iconPosition === "start" ? "start" : glyphAtEnd ? "end" : null,
|
|
69
|
+
padStart: iconPosition === "start",
|
|
70
|
+
padEnd: clearable || iconPosition === "end",
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* What each `search` token resolves to while unset, keyed by token name, for
|
|
76
|
+
* the Studio to preview: the `muted` field the search box falls back to.
|
|
77
|
+
*
|
|
78
|
+
* @param {object} theme — the resolved workspace theme.
|
|
79
|
+
*/
|
|
80
|
+
export function searchScopeDefaults(theme) {
|
|
81
|
+
const field = resolveTextFieldTokens(theme);
|
|
82
|
+
const out = { iconPosition: DEFAULT_SEARCH_ICON_POSITION };
|
|
83
|
+
for (const name of SEARCH_FIELD_LOOK_TOKENS) out[name] = field[name];
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
// The ONE implementation behind the `<SearchField>` SDK primitive. It is
|
|
2
2
|
// `<TextField>` with a search glyph and a clear control, so it wears the same
|
|
3
|
-
// field and takes the same props
|
|
4
|
-
// `search-field.native.js`) differ only in where
|
|
3
|
+
// field and takes the same props, plus the theme's `search` look; the platform
|
|
4
|
+
// bindings (`search-field.js` / `search-field.native.js`) differ only in where
|
|
5
|
+
// the primitives come from.
|
|
5
6
|
|
|
6
7
|
import React from "react";
|
|
7
8
|
import { useHostTheme } from "./hooks.js";
|
|
8
9
|
import { DEFAULT_THEME_TOKENS } from "./_theme-tokens.js";
|
|
9
10
|
import { Icon } from "./icon.js";
|
|
10
11
|
import { resolveFormControlTokens } from "./form-control-tokens.js";
|
|
12
|
+
import { resolveSearchFieldTokens, searchFieldLayout } from "./search-field-tokens.js";
|
|
11
13
|
import {
|
|
12
14
|
errorMessage,
|
|
13
15
|
hasNote,
|
|
@@ -24,6 +26,17 @@ const ADORNMENT_WIDTH = 40;
|
|
|
24
26
|
// A real target: the web host ignores hitSlop, so size carries it.
|
|
25
27
|
const CLEAR_MIN_SIZE = 36;
|
|
26
28
|
|
|
29
|
+
function adornmentSlot(side) {
|
|
30
|
+
return {
|
|
31
|
+
position: "absolute",
|
|
32
|
+
top: 0,
|
|
33
|
+
bottom: 0,
|
|
34
|
+
alignItems: "center",
|
|
35
|
+
justifyContent: "center",
|
|
36
|
+
...(side === "start" ? { left: 0 } : { right: 0 }),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
27
40
|
/**
|
|
28
41
|
* Bind the shared search field to one platform's primitives.
|
|
29
42
|
*
|
|
@@ -45,6 +58,7 @@ export function makeSearchField(rn) {
|
|
|
45
58
|
error,
|
|
46
59
|
disabled = false,
|
|
47
60
|
accessibilityLabel,
|
|
61
|
+
iconPosition,
|
|
48
62
|
style,
|
|
49
63
|
containerStyle,
|
|
50
64
|
testID,
|
|
@@ -52,6 +66,7 @@ export function makeSearchField(rn) {
|
|
|
52
66
|
} = props;
|
|
53
67
|
const theme = useHostTheme() || DEFAULT_THEME_TOKENS;
|
|
54
68
|
const t = resolveFormControlTokens(theme);
|
|
69
|
+
const search = resolveSearchFieldTokens(theme, { iconPosition });
|
|
55
70
|
const inputRef = React.useRef(null);
|
|
56
71
|
const setRef = React.useMemo(() => shareRef(ref, inputRef), [ref]);
|
|
57
72
|
const noteId = React.useId();
|
|
@@ -60,6 +75,8 @@ export function makeSearchField(rn) {
|
|
|
60
75
|
if (input && typeof input.focus === "function") input.focus();
|
|
61
76
|
};
|
|
62
77
|
const hasText = typeof value === "string" && value.length > 0;
|
|
78
|
+
const clearable = hasText && !disabled;
|
|
79
|
+
const layout = searchFieldLayout(search.iconPosition, clearable);
|
|
63
80
|
const clear = () => {
|
|
64
81
|
if (typeof onChangeText === "function") onChangeText("");
|
|
65
82
|
if (typeof onClear === "function") onClear();
|
|
@@ -98,30 +115,30 @@ export function makeSearchField(rn) {
|
|
|
98
115
|
autoCapitalize: "none",
|
|
99
116
|
autoCorrect: false,
|
|
100
117
|
testID,
|
|
118
|
+
// The field's own props beat the theme's search look.
|
|
119
|
+
...search.field,
|
|
101
120
|
...rest,
|
|
102
121
|
style: [
|
|
103
|
-
{ paddingLeft: ADORNMENT_WIDTH },
|
|
104
|
-
|
|
122
|
+
layout.padStart ? { paddingLeft: ADORNMENT_WIDTH } : null,
|
|
123
|
+
layout.padEnd ? { paddingRight: ADORNMENT_WIDTH } : null,
|
|
105
124
|
style,
|
|
106
125
|
],
|
|
107
126
|
}),
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
),
|
|
124
|
-
hasText && !disabled
|
|
127
|
+
layout.glyph
|
|
128
|
+
? h(
|
|
129
|
+
View,
|
|
130
|
+
{
|
|
131
|
+
testID: testID ? `${testID}-icon` : undefined,
|
|
132
|
+
style: {
|
|
133
|
+
...adornmentSlot(layout.glyph),
|
|
134
|
+
width: ADORNMENT_WIDTH,
|
|
135
|
+
pointerEvents: "none",
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
h(Icon, { name: "search", size: GLYPH_SIZE, color: t.placeholderColor }),
|
|
139
|
+
)
|
|
140
|
+
: null,
|
|
141
|
+
clearable
|
|
125
142
|
? h(
|
|
126
143
|
Pressable,
|
|
127
144
|
{
|
|
@@ -131,14 +148,10 @@ export function makeSearchField(rn) {
|
|
|
131
148
|
testID: testID ? `${testID}-clear` : undefined,
|
|
132
149
|
style: ({ pressed }) => [
|
|
133
150
|
{
|
|
134
|
-
|
|
151
|
+
...adornmentSlot("end"),
|
|
135
152
|
right: 2,
|
|
136
|
-
top: 0,
|
|
137
|
-
bottom: 0,
|
|
138
153
|
minWidth: CLEAR_MIN_SIZE,
|
|
139
154
|
minHeight: CLEAR_MIN_SIZE,
|
|
140
|
-
alignItems: "center",
|
|
141
|
-
justifyContent: "center",
|
|
142
155
|
},
|
|
143
156
|
pressed ? { opacity: 0.6 } : null,
|
|
144
157
|
],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colixsystems/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.237.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
|
@@ -275,6 +275,9 @@ function formatThemeColorTokenRef(path, percent) {
|
|
|
275
275
|
// The looks a `tabs.style` token may name; `<Tabs>` draws each.
|
|
276
276
|
const THEME_TAB_STYLES = Object.freeze(["segmented", "pill", "underline"]);
|
|
277
277
|
|
|
278
|
+
// Where `<SearchField>` draws its magnifier; "start" is the look it always had.
|
|
279
|
+
const THEME_SEARCH_ICON_POSITIONS = Object.freeze(["start", "end", "none"]);
|
|
280
|
+
|
|
278
281
|
const THEME_COMPONENT_SHADOWS = Object.freeze(["none", "sm", "md", "lg"]);
|
|
279
282
|
|
|
280
283
|
// sc-3857 — the `textTransform` token's closed enum, matching the per-instance
|
|
@@ -585,7 +588,7 @@ const THEME_COMPONENTS = Object.freeze({
|
|
|
585
588
|
// than its `color` field, which the `text` scope already owns -- two
|
|
586
589
|
// scopes writing the same field on the same widget would race.
|
|
587
590
|
accent: Object.freeze({
|
|
588
|
-
label: "
|
|
591
|
+
label: "Labels",
|
|
589
592
|
tokens: Object.freeze({
|
|
590
593
|
background: Object.freeze({ type: "color", uiDefault: "colors.primarySoft" }),
|
|
591
594
|
borderColor: Object.freeze({ type: "color", uiDefault: "colors.primary" }),
|
|
@@ -602,7 +605,7 @@ const THEME_COMPONENTS = Object.freeze({
|
|
|
602
605
|
// sc-5392 -- the Button widget's danger/delete variant. Separate token set
|
|
603
606
|
// from `button` above so "make the buttons coral" never recolours Delete.
|
|
604
607
|
destructive: Object.freeze({
|
|
605
|
-
label: "
|
|
608
|
+
label: "Danger buttons",
|
|
606
609
|
tokens: Object.freeze({
|
|
607
610
|
background: Object.freeze({ type: "color", uiDefault: "colors.danger" }),
|
|
608
611
|
textColor: Object.freeze({ type: "color", uiDefault: "colors.onPrimary" }),
|
|
@@ -617,7 +620,7 @@ const THEME_COMPONENTS = Object.freeze({
|
|
|
617
620
|
// scope) rather than to Text/Label/Data Value, whose `color` field the
|
|
618
621
|
// `text` scope already owns.
|
|
619
622
|
muted: Object.freeze({
|
|
620
|
-
label: "
|
|
623
|
+
label: "Inputs",
|
|
621
624
|
// The SDK's form controls read this scope directly, in every widget.
|
|
622
625
|
sdkComponents: Object.freeze(["TextField", "Checkbox", "Switch", "Select", "SearchField"]),
|
|
623
626
|
tokens: Object.freeze({
|
|
@@ -636,7 +639,7 @@ const THEME_COMPONENTS = Object.freeze({
|
|
|
636
639
|
// sc-5392 -- dropdown/menu surfaces. The form widgets' choice-field option
|
|
637
640
|
// list is the closest thing the built-in catalog has to a popover panel.
|
|
638
641
|
popover: Object.freeze({
|
|
639
|
-
label: "
|
|
642
|
+
label: "Dropdown menus",
|
|
640
643
|
sdkComponents: Object.freeze(["Select"]),
|
|
641
644
|
tokens: Object.freeze({
|
|
642
645
|
background: Object.freeze({ type: "color", uiDefault: "colors.surface" }),
|
|
@@ -648,11 +651,26 @@ const THEME_COMPONENTS = Object.freeze({
|
|
|
648
651
|
"appstudio.form-builder": POPOVER_FIELDS,
|
|
649
652
|
}),
|
|
650
653
|
}),
|
|
651
|
-
//
|
|
654
|
+
// Search boxes: `<SearchField>` reads this scope directly. Every token is
|
|
655
|
+
// unset by default and falls back to the `muted` field it draws.
|
|
656
|
+
search: Object.freeze({
|
|
657
|
+
label: "Search fields",
|
|
658
|
+
sdkComponents: Object.freeze(["SearchField"]),
|
|
659
|
+
tokens: Object.freeze({
|
|
660
|
+
iconPosition: Object.freeze({ type: "enum", values: THEME_SEARCH_ICON_POSITIONS }),
|
|
661
|
+
// 48 is past half the tallest field, so its ceiling draws a pill.
|
|
662
|
+
radius: Object.freeze({ type: "size", min: 0, max: 48 }),
|
|
663
|
+
background: Object.freeze({ type: "color" }),
|
|
664
|
+
borderColor: Object.freeze({ type: "color" }),
|
|
665
|
+
height: Object.freeze({ type: "size", min: 32, max: 72 }),
|
|
666
|
+
}),
|
|
667
|
+
targets: Object.freeze({}),
|
|
668
|
+
}),
|
|
669
|
+
// sc-5392 -- not a keyboard focus style: a permanent emphasis border
|
|
652
670
|
// on the Button widget, applied after its variant colours and beaten by the
|
|
653
671
|
// widget's own per-instance `borderColor` (REQ-THEME-13 stays final).
|
|
654
672
|
ring: Object.freeze({
|
|
655
|
-
label: "
|
|
673
|
+
label: "Button outline",
|
|
656
674
|
tokens: Object.freeze({
|
|
657
675
|
color: Object.freeze({ type: "color", uiDefault: "colors.primary" }),
|
|
658
676
|
width: Object.freeze({ type: "size", min: 0, max: 8 }),
|
|
@@ -3093,7 +3111,7 @@ const PRIMITIVES = [
|
|
|
3093
3111
|
{
|
|
3094
3112
|
name: "Switch",
|
|
3095
3113
|
description:
|
|
3096
|
-
'Themed on/off toggle. `<Switch label="Email me updates" value={on} onValueChange={setOn} />` — THE 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 >
|
|
3114
|
+
'Themed on/off toggle. `<Switch label="Email me updates" value={on} onValueChange={setOn} />` — THE 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. Never hand-roll a toggle from `Pressable` + a sliding `View`.',
|
|
3097
3115
|
rnComponent: "Switch",
|
|
3098
3116
|
docsUrl: "https://reactnative.dev/docs/switch",
|
|
3099
3117
|
},
|
|
@@ -3202,7 +3220,7 @@ const PRIMITIVES = [
|
|
|
3202
3220
|
{
|
|
3203
3221
|
name: "ConfirmDialog",
|
|
3204
3222
|
description:
|
|
3205
|
-
'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 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. Never hand-build one from a `<Modal>` or an `<Overlay>` with your own two buttons, 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
|
|
3223
|
+
'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 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. Never hand-build one from a `<Modal>` or an `<Overlay>` with your own two buttons, 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.',
|
|
3206
3224
|
rnComponent: null,
|
|
3207
3225
|
docsUrl: null,
|
|
3208
3226
|
},
|
|
@@ -3349,35 +3367,35 @@ const PRIMITIVES = [
|
|
|
3349
3367
|
{
|
|
3350
3368
|
name: "Button",
|
|
3351
3369
|
description:
|
|
3352
|
-
'Themed button. `<Button title="Save" onPress={save} />` — THE way to draw a button: its fill, label colour, border, corner radius, padding, weight, shadow and gradient come from the app\'s button look (
|
|
3370
|
+
'Themed button. `<Button title="Save" onPress={save} />` — THE way to draw 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"), `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. 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. Never hand-roll a button from `Pressable` + background + radius.',
|
|
3353
3371
|
rnComponent: null,
|
|
3354
3372
|
docsUrl: null,
|
|
3355
3373
|
},
|
|
3356
3374
|
{
|
|
3357
3375
|
name: "TextField",
|
|
3358
3376
|
description:
|
|
3359
|
-
'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 (
|
|
3377
|
+
'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`.',
|
|
3360
3378
|
rnComponent: null,
|
|
3361
3379
|
docsUrl: null,
|
|
3362
3380
|
},
|
|
3363
3381
|
{
|
|
3364
3382
|
name: "Checkbox",
|
|
3365
3383
|
description:
|
|
3366
|
-
'Themed tick box. `<Checkbox label="Remember me" value={checked} onValueChange={setChecked} />` — THE 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 >
|
|
3384
|
+
'Themed tick box. `<Checkbox label="Remember me" value={checked} onValueChange={setChecked} />` — THE 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. Never hand-roll a tick box from `Pressable` + a bordered `View` + a "✓".',
|
|
3367
3385
|
rnComponent: null,
|
|
3368
3386
|
docsUrl: null,
|
|
3369
3387
|
},
|
|
3370
3388
|
{
|
|
3371
3389
|
name: "Select",
|
|
3372
3390
|
description:
|
|
3373
|
-
'Themed single-choice picker. `<Select label="Status" value={status} onValueChange={setStatus} options={[{ value: "open", label: "Open" }, { value: "done", label: "Done" }]} placeholder="Choose a status" />` — THE way to let the user pick ONE of a known list. The trigger is the app\'s input field (Design > Components >
|
|
3391
|
+
'Themed single-choice picker. `<Select label="Status" value={status} onValueChange={setStatus} options={[{ value: "open", label: "Open" }, { value: "done", label: "Done" }]} placeholder="Choose a status" />` — THE 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. Never hand-roll a dropdown from `Pressable` + an absolutely positioned list: it is clipped by the widget\'s container on both hosts.',
|
|
3374
3392
|
rnComponent: null,
|
|
3375
3393
|
docsUrl: null,
|
|
3376
3394
|
},
|
|
3377
3395
|
{
|
|
3378
3396
|
name: "SearchField",
|
|
3379
3397
|
description:
|
|
3380
|
-
'Themed search box. `<SearchField value={query} onChangeText={setQuery} placeholder="Search orders" />` — THE way to draw a field that filters or searches. It IS a `TextField` (same
|
|
3398
|
+
'Themed search box. `<SearchField value={query} onChangeText={setQuery} placeholder="Search orders" />` — THE 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 hand-roll a search box from a `TextInput` + an icon, and never add a magnifier of your own.',
|
|
3381
3399
|
rnComponent: null,
|
|
3382
3400
|
docsUrl: null,
|
|
3383
3401
|
},
|
|
@@ -6049,7 +6067,12 @@ const CONTRACT = deepFreeze({
|
|
|
6049
6067
|
// calendar sheet instead of the browser's date popup; both hosts show
|
|
6050
6068
|
// the value in the app's language with a clear button. New optional
|
|
6051
6069
|
// props: placeholder, clearLabel, doneLabel, closeAccessibilityLabel, testID.
|
|
6052
|
-
|
|
6070
|
+
// 1.191.0: additive (sc-11759) — `themeComponents.search` styles every
|
|
6071
|
+
// `<SearchField>`: icon position, corner radius, background, border
|
|
6072
|
+
// colour, height, each falling back to the `muted` field. The field
|
|
6073
|
+
// takes an `iconPosition` prop. Scope labels renamed (sc-11763); keys
|
|
6074
|
+
// unchanged.
|
|
6075
|
+
version: "1.191.0",
|
|
6053
6076
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
6054
6077
|
hooks: HOOKS,
|
|
6055
6078
|
primitives: PRIMITIVES,
|
|
@@ -6065,6 +6088,7 @@ const CONTRACT = deepFreeze({
|
|
|
6065
6088
|
themeColorTokens: THEME_COLOR_TOKENS,
|
|
6066
6089
|
themeComponents: THEME_COMPONENTS,
|
|
6067
6090
|
themeComponentTabStyles: THEME_TAB_STYLES,
|
|
6091
|
+
themeComponentSearchIconPositions: THEME_SEARCH_ICON_POSITIONS,
|
|
6068
6092
|
themeComponentShadows: THEME_COMPONENT_SHADOWS,
|
|
6069
6093
|
themeComponentTextTransforms: THEME_COMPONENT_TEXT_TRANSFORMS,
|
|
6070
6094
|
themeComponentGradient: THEME_COMPONENT_GRADIENT,
|