@colixsystems/widget-sdk 0.212.0 → 0.213.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 +47 -1
- package/dist/button-view.js +5 -3
- package/dist/confirm-dialog-view.js +158 -0
- package/dist/confirm-dialog.js +11 -0
- package/dist/confirm-dialog.native.js +11 -0
- package/dist/contract.cjs +13 -1
- package/dist/contract.js +13 -1
- package/dist/index.d.ts +66 -0
- package/dist/index.js +2 -0
- package/dist/index.native.js +2 -0
- package/package.json +1 -1
- package/src/contract.js +13 -1
package/README.md
CHANGED
|
@@ -83,7 +83,52 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
|
|
|
83
83
|
|
|
84
84
|
## Status
|
|
85
85
|
|
|
86
|
-
`v0.
|
|
86
|
+
`v0.213.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**.
|
|
87
|
+
|
|
88
|
+
### What's new in 0.213.0 (contract 1.172.0)
|
|
89
|
+
|
|
90
|
+
**New component `<ConfirmDialog>` — the standard "are you sure?".** Widgets asked for confirmation each in their own way (a button that re-labels itself to be pressed twice, a hand-laid Yes / No row), so a delete looked and behaved differently from one widget to the next. `<ConfirmDialog>` is that prompt, built once as a preset of `<Modal>` and `<Button>`:
|
|
91
|
+
|
|
92
|
+
```jsx
|
|
93
|
+
import { useState } from "react";
|
|
94
|
+
import { ConfirmDialog, Button, useToast } from "@colixsystems/widget-sdk";
|
|
95
|
+
|
|
96
|
+
function DeleteOrder({ order, onDelete }) {
|
|
97
|
+
const [asking, setAsking] = useState(false);
|
|
98
|
+
const { showToast } = useToast();
|
|
99
|
+
return (
|
|
100
|
+
<>
|
|
101
|
+
<Button variant="danger" title="Delete" onPress={() => setAsking(true)} />
|
|
102
|
+
<ConfirmDialog
|
|
103
|
+
visible={asking}
|
|
104
|
+
tone="destructive"
|
|
105
|
+
title="Delete this order?"
|
|
106
|
+
message="This cannot be undone."
|
|
107
|
+
confirmLabel="Delete"
|
|
108
|
+
onCancel={() => setAsking(false)}
|
|
109
|
+
onConfirm={async () => {
|
|
110
|
+
try {
|
|
111
|
+
await onDelete(order.id);
|
|
112
|
+
} catch (err) {
|
|
113
|
+
showToast({ kind: "error", message: err.message });
|
|
114
|
+
}
|
|
115
|
+
setAsking(false);
|
|
116
|
+
}}
|
|
117
|
+
/>
|
|
118
|
+
</>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Props: `visible`, `title` (the question), `message` (what confirming does; text is drawn in the theme's body style, any other node as it is), `confirmLabel` and `cancelLabel`, `tone` (`default` | `destructive`), `busy`, `onConfirm`, `onCancel`, `closeAccessibilityLabel` and `testID` (the parts take `-message`, `-cancel`, `-confirm` and `-busy`, plus the Modal's own).
|
|
124
|
+
|
|
125
|
+
- **Cancelling is the safe default.** The Cancel button, the close button, a backdrop press, Escape on web and the Android back button all fire `onCancel`, never `onConfirm`.
|
|
126
|
+
- **`tone="destructive"`** draws the confirm button as the danger `<Button>`, so it takes its colours from the theme's `destructive` scope (Design > Components > Destructive), and on web opens with focus on Cancel. `default` draws the primary button and opens with focus on Confirm. Focus placement is web only.
|
|
127
|
+
- **Busy.** Return the action's promise from `onConfirm` and the dialog stays busy until it settles: both buttons are disabled, a `<Spinner>` shows, nothing dismisses the dialog, and a second press is ignored. Pass `busy` yourself when the action runs elsewhere; the two combine. A rejected promise is **not** caught: handle the failure inside `onConfirm`, otherwise it is reported as an unhandled rejection. Either way the dialog becomes usable again.
|
|
128
|
+
- **It never closes itself.** Set `visible` to false from `onCancel` and once the action is done.
|
|
129
|
+
- **Labels follow the app's language.** Unset, the buttons read the app's shared `confirm`, `cancel` and `close` translations (the same ones `useI18n().t("cancel")` resolves), falling back to English. Prefer a `confirmLabel` that names the action: "Delete", "Remove", "Send".
|
|
130
|
+
|
|
131
|
+
It adds no theme scope: the panel follows `modal`, the actions `button`, a destructive confirm `destructive`. `<Button>` now forwards a `ref` to its pressable, which is how the dialog places focus. Additive.
|
|
87
132
|
|
|
88
133
|
### What's new in 0.212.0 (contract 1.171.0)
|
|
89
134
|
|
|
@@ -2550,6 +2595,7 @@ import { defineWidget, validateManifest, useDatastoreQuery, Text, View } from "@
|
|
|
2550
2595
|
- `WidgetTree({ node })` — component that renders an author-authored child node through the host's renderer; used by Tabs / Card / custom containers to host arbitrary child widgets.
|
|
2551
2596
|
- `Text`, `View`, `Pressable`, `Image`, `ScrollView`, `TextInput`, `FlatList`, `SectionList`, `ActivityIndicator`, `Switch`, `StyleSheet`, `Linking`, `Icon`, `DateTimePicker` — re-exported from `react-native` (the RN primitives) or implemented in the SDK (`Icon` wraps `lucide-react-native`; `DateTimePicker` wraps `@react-native-community/datetimepicker` on native and renders `<input type="date|time|datetime-local">` directly on web because the RN library has no react-native-web mapping). The web build aliases `react-native` to `react-native-web` so the RN-re-exported primitives render in the browser without any per-platform code; the exported Expo app's Metro bundler resolves the real `react-native` library. `Linking` is a static API (`Linking.openURL(url)`) — use it for external URLs, and use `useNavigation().goTo(pageId)` for internal page navigation. See https://reactnative.dev/docs/ for per-component props.
|
|
2552
2597
|
- `Overlay` — the screen-level overlay (sc-6607). `<Overlay visible={!!preview} onRequestClose={() => setPreview(null)} size="full">…</Overlay>` renders its children OUTSIDE the widget's layout box on both hosts (web portals it to the document root, native uses the OS modal), so no clipping card, `ScrollView` or neighbouring widget can cut off a document/media preview, lightbox or confirm dialog — which absolute positioning inside the widget cannot achieve on either platform. Children stay in your own React tree, so state and hooks work normally. `onRequestClose` carries the backdrop press, Escape on web, and the Android back button; `size` is `sm` | `md` (default) | `lg` | `full`; the scrim, surface, radius, padding and elevation come from the workspace theme. An anchored dropdown still belongs inside your own root.
|
|
2598
|
+
- `ConfirmDialog` — the standard confirmation prompt, a preset of `Modal` and `Button`. `<ConfirmDialog visible={asking} tone="destructive" title="Delete this order?" message="This cannot be undone." confirmLabel="Delete" onCancel={() => setAsking(false)} onConfirm={removeOrder} />` asks before a delete or any other action that cannot be undone. Cancel, the close button, the backdrop, Escape and the Android back button fire `onCancel`; a promise returned from `onConfirm` keeps it busy (buttons disabled, a second press ignored) until it settles. Props: `visible`, `title`, `message`, `confirmLabel`, `cancelLabel`, `tone` (`default` | `destructive`), `busy`, `onConfirm`, `onCancel`, `closeAccessibilityLabel`, `testID`. Use it instead of building a confirmation from `Modal` yourself.
|
|
2553
2599
|
- `Modal` — the standard dialog, built on `Overlay`. `<Modal visible={open} onRequestClose={() => setOpen(false)} title="Edit order" footer={<Button title="Save" onPress={save} />}>…</Modal>` draws a title, a close button, a body that scrolls when it is long and a footer action row laid out below it. Its background, corner radius, padding, scrim, shadow and title colour come from the theme's `modal` scope (Design > Components > Dialogs), so every dialog in an app matches. Props: `visible`, `onRequestClose`, `dismissible`, `title`, `footer`, `presentation` (`dialog` | `sheet`), `size` (`sm` | `md` | `lg` | `full`), `dismissOnBackdropPress`, `role`, `initialFocusRef`, `closeAccessibilityLabel`, `accessibilityLabel`, `testID`. Use it for every dialog instead of hand-building a header and an action row inside `Overlay`.
|
|
2554
2600
|
- `RichText` — the formatted-content renderer (sc-6970). `<RichText value={post.body} />` parses the markdown subset (`**bold**`, `*italic*` / `_italic_`, `` `code` ``, `#`/`##`/`###` headings, `-`/`*` bullets, `1.` ordered items, one-line `` images) and renders it with SDK primitives, so formatted content reads the same in the web Player and the exported Expo app. `value` is markdown, NOT HTML — widgets have no `dangerouslySetInnerHTML` on either host, so tags in a content string render as literal tag soup; stored text that still holds legacy HTML is stripped to plain text rather than shown as markup. Props: `value`, `renderImage` (`({ src, alt, size }) => node` — supply it when images are filestore ids, because resolving one needs the scopes your own widget holds; without it only absolute `http(s)` URLs render), `style`, `testID`. Colour, type scale and leading come from the workspace theme — never re-style them.
|
|
2555
2601
|
- `MarkdownInput` — the authoring half of `<RichText>` (sc-6970). `<MarkdownInput value={draft} onChange={setDraft} />` is a multi-line field, a toolbar whose buttons wrap the current SELECTION in markdown markers (bold, italic, code, H2, H3, bullets, numbered list, each toggling back off), and a live `<RichText>` preview of the result underneath — the way to let an app USER write formatted text, instead of a bare `TextInput` plus hand-rolled buttons that can only splice literal tags into the value. Props: `value`, `onChange(next)`, `placeholder`, `previewLabel` (default `Preview`), `showPreview` (default true), `renderImage` (passed through to the preview), `minHeight` (150) and `maxHeight` (280) so a long draft scrolls inside its box, `accessibilityLabel`, `style`, `testID`.
|
package/dist/button-view.js
CHANGED
|
@@ -100,8 +100,9 @@ export function makeButton(rn) {
|
|
|
100
100
|
* @param {boolean} [props.hostThemed] — `fields` already carry the theme
|
|
101
101
|
* (a built-in the host folded); skip the second fold, which would add the
|
|
102
102
|
* Button widget's own scopes to a form submit or a Link action.
|
|
103
|
+
* @param {object} ref — forwarded to the pressable, so a caller can focus it.
|
|
103
104
|
*/
|
|
104
|
-
function Button({
|
|
105
|
+
const Button = React.forwardRef(function Button({
|
|
105
106
|
title,
|
|
106
107
|
variant = "primary",
|
|
107
108
|
icon,
|
|
@@ -114,7 +115,7 @@ export function makeButton(rn) {
|
|
|
114
115
|
hostThemed = false,
|
|
115
116
|
accessibilityLabel,
|
|
116
117
|
testID,
|
|
117
|
-
}) {
|
|
118
|
+
}, ref) {
|
|
118
119
|
const theme = useHostTheme() || DEFAULT_THEME_TOKENS;
|
|
119
120
|
const { colors, spacing, radii, typography } = theme;
|
|
120
121
|
const f = hostThemed
|
|
@@ -192,6 +193,7 @@ export function makeButton(rn) {
|
|
|
192
193
|
return React.createElement(
|
|
193
194
|
Pressable,
|
|
194
195
|
{
|
|
196
|
+
ref,
|
|
195
197
|
onPress,
|
|
196
198
|
disabled,
|
|
197
199
|
accessibilityRole: "button",
|
|
@@ -202,7 +204,7 @@ export function makeButton(rn) {
|
|
|
202
204
|
},
|
|
203
205
|
children,
|
|
204
206
|
);
|
|
205
|
-
}
|
|
207
|
+
});
|
|
206
208
|
|
|
207
209
|
Button.displayName = "Button";
|
|
208
210
|
return Button;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// The ONE implementation behind the `<ConfirmDialog>` SDK component: a preset
|
|
2
|
+
// of `<Modal>` holding a message, a Cancel and a Confirm `<Button>`.
|
|
3
|
+
//
|
|
4
|
+
// Bound per host by confirm-dialog.js / confirm-dialog.native.js, which differ
|
|
5
|
+
// only in where the primitives come from, so the two hosts cannot drift.
|
|
6
|
+
|
|
7
|
+
import React from "react";
|
|
8
|
+
import { useHostSharedText, useHostTheme } from "./hooks.js";
|
|
9
|
+
import { DEFAULT_THEME_TOKENS } from "./_theme-tokens.js";
|
|
10
|
+
import { textRoleStyle } from "./text-roles.js";
|
|
11
|
+
|
|
12
|
+
function isThenable(value) {
|
|
13
|
+
return value != null && typeof value.then === "function";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function isText(value) {
|
|
17
|
+
return typeof value === "string" || typeof value === "number";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Bind the shared confirmation to one platform's primitives and to that
|
|
22
|
+
* host's `<Modal>`, `<Button>` and `<Spinner>`.
|
|
23
|
+
*
|
|
24
|
+
* @param {{Text: any}} rn
|
|
25
|
+
* @param {any} Modal — the host's bound `<Modal>`.
|
|
26
|
+
* @param {any} Button — the host's bound `<Button>`.
|
|
27
|
+
* @param {any} Spinner — the host's bound `<Spinner>`.
|
|
28
|
+
*/
|
|
29
|
+
export function makeConfirmDialog(rn, Modal, Button, Spinner) {
|
|
30
|
+
const { Text } = rn;
|
|
31
|
+
|
|
32
|
+
// Rendered inside the Modal, so it reads the theme the Modal re-inks.
|
|
33
|
+
function Message({ testID, children }) {
|
|
34
|
+
const theme = useHostTheme() || DEFAULT_THEME_TOKENS;
|
|
35
|
+
return React.createElement(
|
|
36
|
+
Text,
|
|
37
|
+
{ testID, style: textRoleStyle("body", theme.typography, theme.colors) },
|
|
38
|
+
children,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The standard "are you sure?" prompt. Cancelling is the safe default: the
|
|
44
|
+
* close button, the backdrop, Escape (web) and the Android back button all
|
|
45
|
+
* fire `onCancel`, never `onConfirm`.
|
|
46
|
+
*
|
|
47
|
+
* @param {object} props
|
|
48
|
+
* @param {boolean} [props.visible] — nothing is mounted while false.
|
|
49
|
+
* @param {string} props.title — the question, e.g. "Delete this order?".
|
|
50
|
+
* @param {any} [props.message] — what confirming does; text is drawn in the
|
|
51
|
+
* theme's body style, any other node is rendered as it is.
|
|
52
|
+
* @param {string} [props.confirmLabel] — default the app's "Confirm".
|
|
53
|
+
* @param {string} [props.cancelLabel] — default the app's "Cancel".
|
|
54
|
+
* @param {"default"|"destructive"} [props.tone] — `destructive` draws the
|
|
55
|
+
* confirm button from the theme's Destructive colours and, on web, opens
|
|
56
|
+
* with focus on Cancel; `default` opens with focus on Confirm.
|
|
57
|
+
* @param {boolean} [props.busy] — the confirmed action is running: both
|
|
58
|
+
* buttons are disabled, a loading indicator shows, nothing closes it.
|
|
59
|
+
* @param {() => void|Promise<any>} [props.onConfirm] — a returned promise
|
|
60
|
+
* keeps the dialog busy until it settles. A rejection is not caught.
|
|
61
|
+
* @param {() => void} [props.onCancel]
|
|
62
|
+
* @param {string} [props.closeAccessibilityLabel] — default the app's "Close".
|
|
63
|
+
* @param {string} [props.testID] — on the panel; the parts take `-message`,
|
|
64
|
+
* `-cancel`, `-confirm` and `-busy`, plus the Modal's own.
|
|
65
|
+
*/
|
|
66
|
+
function ConfirmDialog({
|
|
67
|
+
visible = false,
|
|
68
|
+
title,
|
|
69
|
+
message,
|
|
70
|
+
confirmLabel,
|
|
71
|
+
cancelLabel,
|
|
72
|
+
tone = "default",
|
|
73
|
+
busy = false,
|
|
74
|
+
onConfirm,
|
|
75
|
+
onCancel,
|
|
76
|
+
closeAccessibilityLabel,
|
|
77
|
+
testID,
|
|
78
|
+
}) {
|
|
79
|
+
const sharedConfirm = useHostSharedText("confirm", "Confirm");
|
|
80
|
+
const sharedCancel = useHostSharedText("cancel", "Cancel");
|
|
81
|
+
const sharedClose = useHostSharedText("close", "Close");
|
|
82
|
+
const [pending, setPending] = React.useState(false);
|
|
83
|
+
// Set before the re-render, so a second press in the same frame is ignored.
|
|
84
|
+
const inFlight = React.useRef(false);
|
|
85
|
+
const cancelRef = React.useRef(null);
|
|
86
|
+
const confirmRef = React.useRef(null);
|
|
87
|
+
|
|
88
|
+
const working = busy || pending;
|
|
89
|
+
const destructive = tone === "destructive";
|
|
90
|
+
const part = (name) => (testID ? `${testID}-${name}` : undefined);
|
|
91
|
+
|
|
92
|
+
const confirm = () => {
|
|
93
|
+
if (working || inFlight.current || typeof onConfirm !== "function") return;
|
|
94
|
+
const result = onConfirm();
|
|
95
|
+
if (!isThenable(result)) return;
|
|
96
|
+
inFlight.current = true;
|
|
97
|
+
setPending(true);
|
|
98
|
+
const settle = () => {
|
|
99
|
+
inFlight.current = false;
|
|
100
|
+
setPending(false);
|
|
101
|
+
};
|
|
102
|
+
result.then(settle, (error) => {
|
|
103
|
+
settle();
|
|
104
|
+
// Rethrown so a failure the caller did not handle is still reported.
|
|
105
|
+
throw error;
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const cancel = () => {
|
|
110
|
+
if (working || inFlight.current) return;
|
|
111
|
+
if (typeof onCancel === "function") onCancel();
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const footer = React.createElement(
|
|
115
|
+
React.Fragment,
|
|
116
|
+
null,
|
|
117
|
+
working ? React.createElement(Spinner, { testID: part("busy") }) : null,
|
|
118
|
+
React.createElement(Button, {
|
|
119
|
+
ref: cancelRef,
|
|
120
|
+
variant: "secondary",
|
|
121
|
+
title: cancelLabel ?? sharedCancel,
|
|
122
|
+
onPress: cancel,
|
|
123
|
+
disabled: working,
|
|
124
|
+
testID: part("cancel"),
|
|
125
|
+
}),
|
|
126
|
+
React.createElement(Button, {
|
|
127
|
+
ref: confirmRef,
|
|
128
|
+
variant: destructive ? "danger" : "primary",
|
|
129
|
+
title: confirmLabel ?? sharedConfirm,
|
|
130
|
+
onPress: confirm,
|
|
131
|
+
disabled: working,
|
|
132
|
+
testID: part("confirm"),
|
|
133
|
+
}),
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
return React.createElement(
|
|
137
|
+
Modal,
|
|
138
|
+
{
|
|
139
|
+
visible,
|
|
140
|
+
title,
|
|
141
|
+
footer,
|
|
142
|
+
role: "alertdialog",
|
|
143
|
+
size: "sm",
|
|
144
|
+
dismissible: !working,
|
|
145
|
+
onRequestClose: cancel,
|
|
146
|
+
initialFocusRef: destructive ? cancelRef : confirmRef,
|
|
147
|
+
closeAccessibilityLabel: closeAccessibilityLabel ?? sharedClose,
|
|
148
|
+
testID,
|
|
149
|
+
},
|
|
150
|
+
isText(message)
|
|
151
|
+
? React.createElement(Message, { testID: part("message") }, message)
|
|
152
|
+
: message,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
ConfirmDialog.displayName = "ConfirmDialog";
|
|
157
|
+
return ConfirmDialog;
|
|
158
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// `<ConfirmDialog>` (web). Binds ./confirm-dialog-view.js to react-native-web
|
|
2
|
+
// and the web `<Modal>`, `<Button>` and `<Spinner>`; see ./primitives.js for
|
|
3
|
+
// why not the bare specifier.
|
|
4
|
+
|
|
5
|
+
import { Text } from "react-native-web";
|
|
6
|
+
import { makeConfirmDialog } from "./confirm-dialog-view.js";
|
|
7
|
+
import { Modal } from "./modal.js";
|
|
8
|
+
import { Button } from "./button.js";
|
|
9
|
+
import { Spinner } from "./loading.js";
|
|
10
|
+
|
|
11
|
+
export const ConfirmDialog = makeConfirmDialog({ Text }, Modal, Button, Spinner);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// `<ConfirmDialog>` (native). Binds ./confirm-dialog-view.js to React Native's
|
|
2
|
+
// own primitives and the native `<Modal>`, `<Button>` and `<Spinner>`; only the
|
|
3
|
+
// imports differ from ./confirm-dialog.js.
|
|
4
|
+
|
|
5
|
+
import { Text } from "react-native";
|
|
6
|
+
import { makeConfirmDialog } from "./confirm-dialog-view.js";
|
|
7
|
+
import { Modal } from "./modal.native.js";
|
|
8
|
+
import { Button } from "./button.native.js";
|
|
9
|
+
import { Spinner } from "./loading.native.js";
|
|
10
|
+
|
|
11
|
+
export const ConfirmDialog = makeConfirmDialog({ Text }, Modal, Button, Spinner);
|
package/dist/contract.cjs
CHANGED
|
@@ -2920,6 +2920,13 @@ const PRIMITIVES = [
|
|
|
2920
2920
|
rnComponent: null,
|
|
2921
2921
|
docsUrl: null,
|
|
2922
2922
|
},
|
|
2923
|
+
{
|
|
2924
|
+
name: "ConfirmDialog",
|
|
2925
|
+
description:
|
|
2926
|
+
'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 Destructive 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.',
|
|
2927
|
+
rnComponent: null,
|
|
2928
|
+
docsUrl: null,
|
|
2929
|
+
},
|
|
2923
2930
|
// sc-8104 — the in-app emoji picker. Typed emoji always worked; what this
|
|
2924
2931
|
// replaces is being forced out to the OS keyboard to find one.
|
|
2925
2932
|
{
|
|
@@ -5628,7 +5635,12 @@ const CONTRACT = deepFreeze({
|
|
|
5628
5635
|
// switch, so an existing `<Switch>` with no colour props is repainted:
|
|
5629
5636
|
// its tracks follow the theme on both hosts and its "on" thumb is white
|
|
5630
5637
|
// on the web. A caller's `trackColor` / `thumbColor` still win.
|
|
5631
|
-
|
|
5638
|
+
// 1.172.0: additive (sc-10925) — the `ConfirmDialog` primitive: the standard
|
|
5639
|
+
// confirmation, a preset of `Modal` and `Button` (title, message, confirm
|
|
5640
|
+
// and cancel labels, `tone` "default" | "destructive", `busy`). No new
|
|
5641
|
+
// theme scope: the panel reads `modal`, the actions `button` and
|
|
5642
|
+
// `destructive`. `Button` forwards a ref to its pressable.
|
|
5643
|
+
version: "1.172.0",
|
|
5632
5644
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
5633
5645
|
hooks: HOOKS,
|
|
5634
5646
|
primitives: PRIMITIVES,
|
package/dist/contract.js
CHANGED
|
@@ -2920,6 +2920,13 @@ const PRIMITIVES = [
|
|
|
2920
2920
|
rnComponent: null,
|
|
2921
2921
|
docsUrl: null,
|
|
2922
2922
|
},
|
|
2923
|
+
{
|
|
2924
|
+
name: "ConfirmDialog",
|
|
2925
|
+
description:
|
|
2926
|
+
'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 Destructive 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.',
|
|
2927
|
+
rnComponent: null,
|
|
2928
|
+
docsUrl: null,
|
|
2929
|
+
},
|
|
2923
2930
|
// sc-8104 — the in-app emoji picker. Typed emoji always worked; what this
|
|
2924
2931
|
// replaces is being forced out to the OS keyboard to find one.
|
|
2925
2932
|
{
|
|
@@ -5624,7 +5631,12 @@ const CONTRACT = deepFreeze({
|
|
|
5624
5631
|
// switch, so an existing `<Switch>` with no colour props is repainted:
|
|
5625
5632
|
// its tracks follow the theme on both hosts and its "on" thumb is white
|
|
5626
5633
|
// on the web. A caller's `trackColor` / `thumbColor` still win.
|
|
5627
|
-
|
|
5634
|
+
// 1.172.0: additive (sc-10925) — the `ConfirmDialog` primitive: the standard
|
|
5635
|
+
// confirmation, a preset of `Modal` and `Button` (title, message, confirm
|
|
5636
|
+
// and cancel labels, `tone` "default" | "destructive", `busy`). No new
|
|
5637
|
+
// theme scope: the panel reads `modal`, the actions `button` and
|
|
5638
|
+
// `destructive`. `Button` forwards a ref to its pressable.
|
|
5639
|
+
version: "1.172.0",
|
|
5628
5640
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
5629
5641
|
hooks: HOOKS,
|
|
5630
5642
|
primitives: PRIMITIVES,
|
package/dist/index.d.ts
CHANGED
|
@@ -3360,6 +3360,70 @@ export const Modal: (props: {
|
|
|
3360
3360
|
children?: ReactNode;
|
|
3361
3361
|
}) => any;
|
|
3362
3362
|
|
|
3363
|
+
export type ConfirmDialogTone = "default" | "destructive";
|
|
3364
|
+
|
|
3365
|
+
/**
|
|
3366
|
+
* The standard confirmation prompt: a preset of `<Modal>` with a message, a
|
|
3367
|
+
* Cancel and a Confirm `<Button>`. Use it before a delete or any other action
|
|
3368
|
+
* that cannot be undone, so every confirmation in the app looks and behaves
|
|
3369
|
+
* the same. Cancelling is the safe default: the Cancel button, the close
|
|
3370
|
+
* button, the backdrop, Escape (web) and the Android back button all fire
|
|
3371
|
+
* `onCancel`, never `onConfirm`.
|
|
3372
|
+
*
|
|
3373
|
+
* It adds no theme scope: the panel follows `modal`, the actions `button`,
|
|
3374
|
+
* and a destructive confirm the `destructive` scope. It never closes itself;
|
|
3375
|
+
* set `visible` false from `onCancel` and once the action is done.
|
|
3376
|
+
*
|
|
3377
|
+
* @example
|
|
3378
|
+
* <ConfirmDialog
|
|
3379
|
+
* visible={!!pending}
|
|
3380
|
+
* tone="destructive"
|
|
3381
|
+
* title="Delete this order?"
|
|
3382
|
+
* message="This cannot be undone."
|
|
3383
|
+
* confirmLabel="Delete"
|
|
3384
|
+
* onCancel={() => setPending(null)}
|
|
3385
|
+
* onConfirm={async () => {
|
|
3386
|
+
* await remove(pending.id);
|
|
3387
|
+
* setPending(null);
|
|
3388
|
+
* }}
|
|
3389
|
+
* />
|
|
3390
|
+
*/
|
|
3391
|
+
export const ConfirmDialog: (props: {
|
|
3392
|
+
/** Nothing renders (and nothing is mounted) while this is false. */
|
|
3393
|
+
visible?: boolean;
|
|
3394
|
+
/** The question, e.g. "Delete this order?". */
|
|
3395
|
+
title: string;
|
|
3396
|
+
/** What confirming does. Text is drawn in the theme's body style. */
|
|
3397
|
+
message?: ReactNode;
|
|
3398
|
+
/** Defaults to the app's translated "Confirm". Name the action instead. */
|
|
3399
|
+
confirmLabel?: string;
|
|
3400
|
+
/** Defaults to the app's translated "Cancel". */
|
|
3401
|
+
cancelLabel?: string;
|
|
3402
|
+
/**
|
|
3403
|
+
* `destructive` draws the confirm button from the theme's Destructive
|
|
3404
|
+
* colours and, on web, opens with focus on Cancel. `default` opens with
|
|
3405
|
+
* focus on Confirm. Focus placement is web only.
|
|
3406
|
+
*/
|
|
3407
|
+
tone?: ConfirmDialogTone;
|
|
3408
|
+
/**
|
|
3409
|
+
* The confirmed action is running: both buttons are disabled, a loading
|
|
3410
|
+
* indicator shows, and the backdrop, Escape and Android back do nothing.
|
|
3411
|
+
*/
|
|
3412
|
+
busy?: boolean;
|
|
3413
|
+
/**
|
|
3414
|
+
* Return the action's promise and the dialog stays busy until it settles,
|
|
3415
|
+
* ignoring a second press. A rejection is not caught: handle the failure
|
|
3416
|
+
* here, or it is reported as an unhandled rejection.
|
|
3417
|
+
*/
|
|
3418
|
+
onConfirm?: () => void | Promise<unknown>;
|
|
3419
|
+
/** The Cancel and close buttons, backdrop, Escape (web) and Android back. */
|
|
3420
|
+
onCancel?: () => void;
|
|
3421
|
+
/** Label for the close button. Defaults to the app's translated "Close". */
|
|
3422
|
+
closeAccessibilityLabel?: string;
|
|
3423
|
+
/** On the panel; the parts take `-message`, `-cancel`, `-confirm` and `-busy`. */
|
|
3424
|
+
testID?: string;
|
|
3425
|
+
}) => any;
|
|
3426
|
+
|
|
3363
3427
|
/** Per-list row overrides; unset values follow Theme Settings > Lists. */
|
|
3364
3428
|
export interface ListRowTokens {
|
|
3365
3429
|
rowPadding?: number;
|
|
@@ -3616,6 +3680,8 @@ export const Button: (props: {
|
|
|
3616
3680
|
labelStyle?: any;
|
|
3617
3681
|
accessibilityLabel?: string;
|
|
3618
3682
|
testID?: string;
|
|
3683
|
+
/** Forwarded to the pressable, so you can focus the button. */
|
|
3684
|
+
ref?: { current: any } | ((node: any) => void);
|
|
3619
3685
|
}) => any;
|
|
3620
3686
|
|
|
3621
3687
|
/**
|
package/dist/index.js
CHANGED
|
@@ -282,6 +282,8 @@ export { Spinner, Skeleton } from "./loading.js";
|
|
|
282
282
|
export { Banner } from "./banner.js";
|
|
283
283
|
// The standard dialog, on `<Overlay>`; follows the theme's `modal` scope.
|
|
284
284
|
export { Modal } from "./modal.js";
|
|
285
|
+
// The standard confirmation, a preset of `<Modal>` and `<Button>`.
|
|
286
|
+
export { ConfirmDialog } from "./confirm-dialog.js";
|
|
285
287
|
// The semantic text tiers, shared with the built-in widgets.
|
|
286
288
|
export {
|
|
287
289
|
textRoleStyle,
|
package/dist/index.native.js
CHANGED
|
@@ -275,6 +275,8 @@ export { Spinner, Skeleton } from "./loading.native.js";
|
|
|
275
275
|
export { Banner } from "./banner.native.js";
|
|
276
276
|
// The standard dialog, on `<Overlay>`; follows the theme's `modal` scope.
|
|
277
277
|
export { Modal } from "./modal.native.js";
|
|
278
|
+
// The standard confirmation, a preset of `<Modal>` and `<Button>`.
|
|
279
|
+
export { ConfirmDialog } from "./confirm-dialog.native.js";
|
|
278
280
|
// The semantic text tiers, shared with the built-in widgets.
|
|
279
281
|
export {
|
|
280
282
|
textRoleStyle,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colixsystems/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.213.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
|
@@ -2920,6 +2920,13 @@ const PRIMITIVES = [
|
|
|
2920
2920
|
rnComponent: null,
|
|
2921
2921
|
docsUrl: null,
|
|
2922
2922
|
},
|
|
2923
|
+
{
|
|
2924
|
+
name: "ConfirmDialog",
|
|
2925
|
+
description:
|
|
2926
|
+
'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 Destructive 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.',
|
|
2927
|
+
rnComponent: null,
|
|
2928
|
+
docsUrl: null,
|
|
2929
|
+
},
|
|
2923
2930
|
// sc-8104 — the in-app emoji picker. Typed emoji always worked; what this
|
|
2924
2931
|
// replaces is being forced out to the OS keyboard to find one.
|
|
2925
2932
|
{
|
|
@@ -5624,7 +5631,12 @@ const CONTRACT = deepFreeze({
|
|
|
5624
5631
|
// switch, so an existing `<Switch>` with no colour props is repainted:
|
|
5625
5632
|
// its tracks follow the theme on both hosts and its "on" thumb is white
|
|
5626
5633
|
// on the web. A caller's `trackColor` / `thumbColor` still win.
|
|
5627
|
-
|
|
5634
|
+
// 1.172.0: additive (sc-10925) — the `ConfirmDialog` primitive: the standard
|
|
5635
|
+
// confirmation, a preset of `Modal` and `Button` (title, message, confirm
|
|
5636
|
+
// and cancel labels, `tone` "default" | "destructive", `busy`). No new
|
|
5637
|
+
// theme scope: the panel reads `modal`, the actions `button` and
|
|
5638
|
+
// `destructive`. `Button` forwards a ref to its pressable.
|
|
5639
|
+
version: "1.172.0",
|
|
5628
5640
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
5629
5641
|
hooks: HOOKS,
|
|
5630
5642
|
primitives: PRIMITIVES,
|