@colixsystems/widget-sdk 0.201.0 → 0.202.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 CHANGED
@@ -83,7 +83,24 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
83
83
 
84
84
  ## Status
85
85
 
86
- `v0.201.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**.
86
+ `v0.202.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.202.0 (contract 1.161.0)
89
+
90
+ **Themed collapsible sections: `Accordion` and `AccordionItem`.** Widgets each built their own expand/collapse section, with their own header, chevron and spacing, and none told a screen reader whether the section was open. The SDK now draws one, from the app's existing look: the surface from **Theme Settings > Cards** (background, border, corner radius) and the headers and dividers from **Theme Settings > Lists** (row padding, row height, divider). No new theme scope.
91
+
92
+ ```jsx
93
+ <Accordion defaultValue="shipping">
94
+ <AccordionItem value="shipping" title="Shipping" subtitle="2-4 days">
95
+ <Text>…</Text>
96
+ </AccordionItem>
97
+ <AccordionItem value="returns" title="Returns" trailing={<Badge label="New" tone="info" />}>
98
+ <Text>…</Text>
99
+ </AccordionItem>
100
+ </Accordion>
101
+ ```
102
+
103
+ By default one section is open at a time and pressing the open one closes it; `multiple` lets several stay open. Pass `value` + `onChange` to control it (a key, or an array of keys when `multiple`), or `defaultValue` to leave it uncontrolled. Each header is a 44px button with a chevron that works from the keyboard and reports `aria-expanded`. `trailing` sits outside the header's toggle, so a button there runs its own action. A section's body mounts only while it is open, so fetch its data inside it. One view renders on both the web Player and the exported app. Additive.
87
104
 
88
105
  ### What's new in 0.201.0 (contract 1.160.0)
89
106
 
@@ -2393,6 +2410,7 @@ import { defineWidget, validateManifest, useDatastoreQuery, Text, View } from "@
2393
2410
  - `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`.
2394
2411
  - `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`.
2395
2412
  - `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 Theme Settings (Cards, Buttons, Muted); never hand-roll one from `View` / `Pressable` / a bare `TextInput`.
2413
+ - `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.
2396
2414
  - `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.
2397
2415
  - `textRoleStyle(role, typography, colors)`, `TEXT_ROLES` — the style of a semantic text tier, shared with the built-in widgets.
2398
2416
  - `WidgetContextProvider` — React context provider that the host (Studio, Player, exported app) wraps widgets with.
@@ -0,0 +1,54 @@
1
+ // The values `<Accordion>` paints with, and its open-state rules. The surface
2
+ // comes from the theme's `card` scope and the rows from its `list` scope, so a
3
+ // look restyles every collapsible section without a scope of its own.
4
+ // Platform-free, so both hosts draw from the same numbers and tests can pin
5
+ // them without a renderer.
6
+
7
+ import { DEFAULT_THEME_TOKENS } from "./_theme-tokens.js";
8
+ import { mixHex, isHexColor } from "./contract.js";
9
+ import { normaliseThemeComponents } from "./theme-components.js";
10
+ import { resolveListTokens, LIST_ROW_MIN_HEIGHT } from "./list-tokens.js";
11
+
12
+ /**
13
+ * Resolve one accordion: the `card` scope's surface, the `list` scope's row
14
+ * padding, height and divider, then the theme tokens.
15
+ *
16
+ * @param {object} theme — the resolved workspace theme (`useTheme()` shape).
17
+ */
18
+ export function resolveAccordionTokens(theme) {
19
+ const base = theme && typeof theme === "object" ? theme : DEFAULT_THEME_TOKENS;
20
+ const colors = base.colors || DEFAULT_THEME_TOKENS.colors;
21
+ const radii = base.radii || DEFAULT_THEME_TOKENS.radii;
22
+ const card = normaliseThemeComponents(base.components).card || {};
23
+ const list = resolveListTokens(base);
24
+
25
+ const background = card.background ?? colors.surface;
26
+ return {
27
+ background,
28
+ borderColor: card.borderColor ?? colors.border,
29
+ radius: card.radius ?? radii.md,
30
+ headerPadding: list.rowPadding,
31
+ // A dense look may shrink a row, never the header's tap target.
32
+ headerMinHeight: Math.max(list.rowMinHeight, LIST_ROW_MIN_HEIGHT),
33
+ dividerColor: list.dividerColor,
34
+ dividerWidth: list.dividerWidth,
35
+ // The mix needs hex; a translucent surface tints the theme's own instead.
36
+ pressedBackground: mixHex(colors.onSurface, isHexColor(background) ? background : colors.surface, 0.94),
37
+ chevronColor: colors.onSurfaceMuted,
38
+ };
39
+ }
40
+
41
+ /** The open sections as an array, from a single key or a list of keys. */
42
+ export function openKeysOf(value) {
43
+ if (Array.isArray(value)) return value.filter((key) => key !== null && key !== undefined);
44
+ return value === null || value === undefined ? [] : [value];
45
+ }
46
+
47
+ /**
48
+ * The open sections after one header is pressed. Single mode keeps at most one
49
+ * open, and pressing the open one closes it.
50
+ */
51
+ export function toggleOpenKey(open, key, multiple) {
52
+ if (open.includes(key)) return open.filter((k) => k !== key);
53
+ return multiple ? [...open, key] : [key];
54
+ }
@@ -0,0 +1,210 @@
1
+ // The ONE implementation behind the `<Accordion>` and `<AccordionItem>` SDK
2
+ // primitives. The platform bindings (`accordion.js` / `accordion.native.js`)
3
+ // differ only in where the primitives come from, so the hosts cannot drift.
4
+ //
5
+ // Widgets each built their own collapsible section, with their own header,
6
+ // chevron and spacing, and none announced whether it was open. This one reads
7
+ // the theme's `card` and `list` scopes, so every section in an app matches.
8
+
9
+ import React from "react";
10
+ import { useHostTheme } from "./hooks.js";
11
+ import { DEFAULT_THEME_TOKENS } from "./_theme-tokens.js";
12
+ import { Icon } from "./icon.js";
13
+ import { textRoleStyle } from "./text-roles.js";
14
+ import { resolveAccordionTokens, openKeysOf, toggleOpenKey } from "./accordion-tokens.js";
15
+
16
+ /**
17
+ * Bind the shared accordion to one platform's primitives.
18
+ *
19
+ * @param {{View: any, Text: any, Pressable: any}} rn
20
+ */
21
+ export function makeAccordion(rn) {
22
+ const { View, Text, Pressable } = rn;
23
+ const AccordionContext = React.createContext(null);
24
+
25
+ /**
26
+ * @param {boolean} [props.multiple] — several sections may be open at once.
27
+ * @param {string|string[]|null} [props.value] — the open section(s); makes
28
+ * the accordion controlled. A key in single mode, an array in multiple.
29
+ * @param {string|string[]|null} [props.defaultValue] — the initially open
30
+ * section(s) when uncontrolled.
31
+ * @param {(next: string|string[]|null) => void} [props.onChange] — the open
32
+ * section(s) after a header press, in the same shape as `value`.
33
+ */
34
+ function Accordion({
35
+ multiple = false,
36
+ value,
37
+ defaultValue,
38
+ onChange,
39
+ children,
40
+ accessibilityLabel,
41
+ style,
42
+ testID,
43
+ }) {
44
+ const theme = useHostTheme() || DEFAULT_THEME_TOKENS;
45
+ const tokens = resolveAccordionTokens(theme);
46
+ const controlled = value !== undefined;
47
+ const [ownOpen, setOwnOpen] = React.useState(() => openKeysOf(defaultValue));
48
+ const open = controlled ? openKeysOf(value) : ownOpen;
49
+
50
+ const toggle = (key) => {
51
+ if (key === null || key === undefined) return;
52
+ const next = toggleOpenKey(open, key, multiple);
53
+ if (!controlled) setOwnOpen(next);
54
+ if (typeof onChange === "function") onChange(multiple ? next : (next[0] ?? null));
55
+ };
56
+
57
+ return React.createElement(
58
+ AccordionContext.Provider,
59
+ { value: { open, toggle, tokens, theme } },
60
+ React.createElement(
61
+ View,
62
+ {
63
+ style: [
64
+ {
65
+ backgroundColor: tokens.background,
66
+ borderColor: tokens.borderColor,
67
+ borderWidth: 1,
68
+ borderRadius: tokens.radius,
69
+ overflow: "hidden",
70
+ },
71
+ style,
72
+ ],
73
+ // A label names the group only when it has a role to hang on.
74
+ role: accessibilityLabel ? "group" : undefined,
75
+ accessibilityLabel,
76
+ testID,
77
+ },
78
+ // The last section's divider slides under the clipped edge, so it
79
+ // ends on the surface wherever it sits in the tree.
80
+ React.createElement(
81
+ View,
82
+ { style: { marginBottom: -tokens.dividerWidth } },
83
+ children,
84
+ ),
85
+ ),
86
+ );
87
+ }
88
+ Accordion.displayName = "Accordion";
89
+
90
+ function AccordionItem(props) {
91
+ const context = React.useContext(AccordionContext);
92
+ // Stable per section, so a wrapped or conditional one keeps its own key.
93
+ const ownKey = React.useId();
94
+ // A lone section is an accordion of one, so it still opens and closes.
95
+ if (!context) return React.createElement(Accordion, null, React.createElement(AccordionItem, props));
96
+
97
+ const {
98
+ value = ownKey,
99
+ title,
100
+ subtitle,
101
+ trailing,
102
+ disabled = false,
103
+ divider = true,
104
+ children,
105
+ accessibilityLabel,
106
+ style,
107
+ testID,
108
+ } = props;
109
+ const { open: openKeys, toggle, tokens, theme } = context;
110
+ const open = openKeys.includes(value);
111
+ const { colors, typography } = theme;
112
+
113
+ const header = React.createElement(
114
+ View,
115
+ {
116
+ style: {
117
+ flexDirection: "row",
118
+ alignItems: "center",
119
+ paddingRight: trailing ? tokens.headerPadding : 0,
120
+ },
121
+ },
122
+ React.createElement(
123
+ Pressable,
124
+ {
125
+ onPress: () => toggle(value),
126
+ disabled,
127
+ // accessibilityRole + aria-*: the pair that announces on both hosts.
128
+ accessibilityRole: "button",
129
+ accessibilityLabel: accessibilityLabel ?? headerLabel(title, subtitle),
130
+ "aria-expanded": open,
131
+ "aria-disabled": disabled,
132
+ testID: testID ? `${testID}-header` : undefined,
133
+ style: ({ pressed }) => [
134
+ {
135
+ flex: 1,
136
+ minWidth: 0,
137
+ flexDirection: "row",
138
+ alignItems: "center",
139
+ gap: theme.spacing.sm,
140
+ minHeight: tokens.headerMinHeight,
141
+ padding: tokens.headerPadding,
142
+ },
143
+ pressed ? { backgroundColor: tokens.pressedBackground } : null,
144
+ disabled ? { opacity: 0.5 } : null,
145
+ ],
146
+ },
147
+ React.createElement(Icon, {
148
+ name: open ? "chevron-down" : "chevron-right",
149
+ size: 18,
150
+ color: tokens.chevronColor,
151
+ }),
152
+ React.createElement(
153
+ View,
154
+ { style: { flex: 1, minWidth: 0, gap: 2 } },
155
+ title !== undefined && title !== null
156
+ ? React.createElement(
157
+ Text,
158
+ { style: textRoleStyle("subtitle", typography, colors), numberOfLines: 1 },
159
+ title,
160
+ )
161
+ : null,
162
+ subtitle !== undefined && subtitle !== null
163
+ ? React.createElement(
164
+ Text,
165
+ { style: textRoleStyle("caption", typography, colors), numberOfLines: 2 },
166
+ subtitle,
167
+ )
168
+ : null,
169
+ ),
170
+ ),
171
+ // Outside the toggle, so a button here never opens the section.
172
+ trailing || null,
173
+ );
174
+
175
+ return React.createElement(
176
+ View,
177
+ {
178
+ style: [
179
+ divider && tokens.dividerWidth > 0
180
+ ? { borderBottomWidth: tokens.dividerWidth, borderColor: tokens.dividerColor }
181
+ : null,
182
+ style,
183
+ ],
184
+ testID,
185
+ },
186
+ header,
187
+ // Mounted only while open, so a closed section costs nothing.
188
+ open
189
+ ? React.createElement(
190
+ View,
191
+ {
192
+ style: { padding: tokens.headerPadding, paddingTop: 0 },
193
+ testID: testID ? `${testID}-panel` : undefined,
194
+ },
195
+ // A section nested in this body is not one of this accordion's.
196
+ React.createElement(AccordionContext.Provider, { value: null }, children),
197
+ )
198
+ : null,
199
+ );
200
+ }
201
+ AccordionItem.displayName = "AccordionItem";
202
+
203
+ // The label replaces the visible text, so it must carry the subtitle too.
204
+ function headerLabel(title, subtitle) {
205
+ const parts = [title, subtitle].filter((part) => typeof part === "string" && part !== "");
206
+ return parts.length ? parts.join(", ") : undefined;
207
+ }
208
+
209
+ return { Accordion, AccordionItem };
210
+ }
@@ -0,0 +1,7 @@
1
+ // `<Accordion>` / `<AccordionItem>` (web). Binds ./accordion-view.js to
2
+ // react-native-web; see ./primitives.js for why not the bare specifier.
3
+
4
+ import { View, Text, Pressable } from "react-native-web";
5
+ import { makeAccordion } from "./accordion-view.js";
6
+
7
+ export const { Accordion, AccordionItem } = makeAccordion({ View, Text, Pressable });
@@ -0,0 +1,7 @@
1
+ // `<Accordion>` / `<AccordionItem>` (native). Binds ./accordion-view.js to
2
+ // React Native's own primitives; only the import differs from ./accordion.js.
3
+
4
+ import { View, Text, Pressable } from "react-native";
5
+ import { makeAccordion } from "./accordion-view.js";
6
+
7
+ export const { Accordion, AccordionItem } = makeAccordion({ View, Text, Pressable });
package/dist/contract.cjs CHANGED
@@ -1450,7 +1450,7 @@ const HOOKS = [
1450
1450
  message: "string | null",
1451
1451
  signerName: "string | null",
1452
1452
  signedAt: "string | null",
1453
- verdict: "{ valid, checks, content_status, ... } | null",
1453
+ verdict: "{ valid, checks, content_status, signed_by_others_since, seal, ... } | null",
1454
1454
  loading: "boolean",
1455
1455
  error: "PermissionError | null // { code, message, retryable }",
1456
1456
  initiate: "() => Promise<{ signature_id, qr, auto_start_token, status }>",
@@ -2804,6 +2804,20 @@ const PRIMITIVES = [
2804
2804
  rnComponent: null,
2805
2805
  docsUrl: null,
2806
2806
  },
2807
+ {
2808
+ name: "Accordion",
2809
+ description:
2810
+ 'Themed collapsible sections. `<Accordion defaultValue="shipping"><AccordionItem value="shipping" title="Shipping" subtitle="2-4 days">…</AccordionItem><AccordionItem value="returns" title="Returns">…</AccordionItem></Accordion>`. THE way to build a section that expands and collapses (an FAQ, a settings group, a record\'s details, a group whose members show on demand): the surface takes the app\'s card look (Theme Settings > Cards: background, border, corner radius) and the headers and dividers its list look (Theme Settings > Lists: row padding, row height, divider), so every collapsible section in the app matches. Each header has a chevron, a 44px touch target, works from the keyboard and tells assistive technology whether its section is open, so never hand-roll a collapsible from `Pressable` + a chevron + a `useState`. By default one section is open at a time and pressing the open one closes it; `multiple` lets several stay open. Props: `multiple`, `value` (the open key, or an array of keys when `multiple`; makes it controlled), `defaultValue` (the same shape, uncontrolled), `onChange(next)` (the open key(s) after a press, in the same shape), `accessibilityLabel`, `style`, `testID`. Its children are `<AccordionItem>`s.',
2811
+ rnComponent: null,
2812
+ docsUrl: null,
2813
+ },
2814
+ {
2815
+ name: "AccordionItem",
2816
+ description:
2817
+ 'One section of an `<Accordion>`. `<AccordionItem value={group.id} title={group.name} subtitle={`${group.count} members`} trailing={<Button title="Delete" variant="danger" onPress={remove} />}>…</AccordionItem>`. Props: `value` (the key `value` / `defaultValue` / `onChange` name it by — give one whenever you name sections; an item may sit inside your own component or a fragment), `title`, `subtitle` (the subtitle / caption text tiers), `trailing` (a node beside the header and OUTSIDE its toggle, so a button there runs its own action without opening the section), `disabled`, `divider` (default true; the surface edge hides the rule under the last section), `children` (the body: it mounts only while the section is open, so fetch its data inside it; an `<AccordionItem>` placed in a body is its own section, never part of the outer accordion), `accessibilityLabel` (defaults to the string `title` and `subtitle`), `style`, `testID` (also tags the header `<testID>-header` and the open body `<testID>-panel`). On its own, outside an `<Accordion>`, it is an accordion of one.',
2818
+ rnComponent: null,
2819
+ docsUrl: null,
2820
+ },
2807
2821
  {
2808
2822
  name: "Tabs",
2809
2823
  description:
@@ -5322,7 +5336,10 @@ const CONTRACT = deepFreeze({
5322
5336
  // `usePageBadge(null)` now means "not known yet" and keeps the current
5323
5337
  // badge; `undefined` still clears it. Linter rules `badge-counter-export-missing`
5324
5338
  // (error) and `badge-counter-undeclared` (warning).
5325
- version: "1.160.0",
5339
+ // 1.161.0: additive (sc-10930) — `Accordion` / `AccordionItem`, themed
5340
+ // collapsible sections drawn from the `card` and `list` scopes: single
5341
+ // or multiple open, controlled or uncontrolled, `aria-expanded` headers.
5342
+ version: "1.161.0",
5326
5343
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
5327
5344
  hooks: HOOKS,
5328
5345
  primitives: PRIMITIVES,
package/dist/contract.js CHANGED
@@ -1450,7 +1450,7 @@ const HOOKS = [
1450
1450
  message: "string | null",
1451
1451
  signerName: "string | null",
1452
1452
  signedAt: "string | null",
1453
- verdict: "{ valid, checks, content_status, ... } | null",
1453
+ verdict: "{ valid, checks, content_status, signed_by_others_since, seal, ... } | null",
1454
1454
  loading: "boolean",
1455
1455
  error: "PermissionError | null // { code, message, retryable }",
1456
1456
  initiate: "() => Promise<{ signature_id, qr, auto_start_token, status }>",
@@ -2804,6 +2804,20 @@ const PRIMITIVES = [
2804
2804
  rnComponent: null,
2805
2805
  docsUrl: null,
2806
2806
  },
2807
+ {
2808
+ name: "Accordion",
2809
+ description:
2810
+ 'Themed collapsible sections. `<Accordion defaultValue="shipping"><AccordionItem value="shipping" title="Shipping" subtitle="2-4 days">…</AccordionItem><AccordionItem value="returns" title="Returns">…</AccordionItem></Accordion>`. THE way to build a section that expands and collapses (an FAQ, a settings group, a record\'s details, a group whose members show on demand): the surface takes the app\'s card look (Theme Settings > Cards: background, border, corner radius) and the headers and dividers its list look (Theme Settings > Lists: row padding, row height, divider), so every collapsible section in the app matches. Each header has a chevron, a 44px touch target, works from the keyboard and tells assistive technology whether its section is open, so never hand-roll a collapsible from `Pressable` + a chevron + a `useState`. By default one section is open at a time and pressing the open one closes it; `multiple` lets several stay open. Props: `multiple`, `value` (the open key, or an array of keys when `multiple`; makes it controlled), `defaultValue` (the same shape, uncontrolled), `onChange(next)` (the open key(s) after a press, in the same shape), `accessibilityLabel`, `style`, `testID`. Its children are `<AccordionItem>`s.',
2811
+ rnComponent: null,
2812
+ docsUrl: null,
2813
+ },
2814
+ {
2815
+ name: "AccordionItem",
2816
+ description:
2817
+ 'One section of an `<Accordion>`. `<AccordionItem value={group.id} title={group.name} subtitle={`${group.count} members`} trailing={<Button title="Delete" variant="danger" onPress={remove} />}>…</AccordionItem>`. Props: `value` (the key `value` / `defaultValue` / `onChange` name it by — give one whenever you name sections; an item may sit inside your own component or a fragment), `title`, `subtitle` (the subtitle / caption text tiers), `trailing` (a node beside the header and OUTSIDE its toggle, so a button there runs its own action without opening the section), `disabled`, `divider` (default true; the surface edge hides the rule under the last section), `children` (the body: it mounts only while the section is open, so fetch its data inside it; an `<AccordionItem>` placed in a body is its own section, never part of the outer accordion), `accessibilityLabel` (defaults to the string `title` and `subtitle`), `style`, `testID` (also tags the header `<testID>-header` and the open body `<testID>-panel`). On its own, outside an `<Accordion>`, it is an accordion of one.',
2818
+ rnComponent: null,
2819
+ docsUrl: null,
2820
+ },
2807
2821
  {
2808
2822
  name: "Tabs",
2809
2823
  description:
@@ -5318,7 +5332,10 @@ const CONTRACT = deepFreeze({
5318
5332
  // `usePageBadge(null)` now means "not known yet" and keeps the current
5319
5333
  // badge; `undefined` still clears it. Linter rules `badge-counter-export-missing`
5320
5334
  // (error) and `badge-counter-undeclared` (warning).
5321
- version: "1.160.0",
5335
+ // 1.161.0: additive (sc-10930) — `Accordion` / `AccordionItem`, themed
5336
+ // collapsible sections drawn from the `card` and `list` scopes: single
5337
+ // or multiple open, controlled or uncontrolled, `aria-expanded` headers.
5338
+ version: "1.161.0",
5322
5339
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
5323
5340
  hooks: HOOKS,
5324
5341
  primitives: PRIMITIVES,
package/dist/index.d.ts CHANGED
@@ -3441,6 +3441,52 @@ export const Tabs: (props: {
3441
3441
  testID?: string;
3442
3442
  }) => any;
3443
3443
 
3444
+ /**
3445
+ * Themed collapsible sections. The surface comes from the theme's `card`
3446
+ * scope and the headers and dividers from its `list` scope, so every
3447
+ * collapsible section in the app matches. Each header announces whether its
3448
+ * section is open, and a section's body mounts only while it is open.
3449
+ *
3450
+ * @example
3451
+ * <Accordion defaultValue="shipping">
3452
+ * <AccordionItem value="shipping" title="Shipping" subtitle="2-4 days">…</AccordionItem>
3453
+ * <AccordionItem value="returns" title="Returns">…</AccordionItem>
3454
+ * </Accordion>
3455
+ */
3456
+ export const Accordion: (props: {
3457
+ /** Several sections may be open at once; by default opening one closes the other. */
3458
+ multiple?: boolean;
3459
+ /** The open section key (an array of keys when `multiple`); makes it controlled. */
3460
+ value?: string | string[] | null;
3461
+ /** The initially open section(s) when uncontrolled. */
3462
+ defaultValue?: string | string[] | null;
3463
+ /** The open section(s) after a header press, in the same shape as `value`. */
3464
+ onChange?: (next: string | string[] | null) => void;
3465
+ children?: ReactNode;
3466
+ /** Names the group of sections for assistive technology. */
3467
+ accessibilityLabel?: string;
3468
+ style?: any;
3469
+ testID?: string;
3470
+ }) => any;
3471
+
3472
+ /** One section of an `<Accordion>`; on its own it is an accordion of one. */
3473
+ export const AccordionItem: (props: {
3474
+ /** The key `value` / `defaultValue` / `onChange` name it by; give one whenever you name sections. */
3475
+ value?: string;
3476
+ title?: ReactNode;
3477
+ subtitle?: ReactNode;
3478
+ /** Beside the header and outside its toggle, so a button here does its own thing. */
3479
+ trailing?: ReactNode;
3480
+ disabled?: boolean;
3481
+ /** Rule under the section (default true); the surface edge hides the last one's. */
3482
+ divider?: boolean;
3483
+ children?: ReactNode;
3484
+ accessibilityLabel?: string;
3485
+ style?: any;
3486
+ /** Also tags the header (`<testID>-header`) and open body (`<testID>-panel`). */
3487
+ testID?: string;
3488
+ }) => any;
3489
+
3444
3490
  export type TextRole = "display" | "title" | "subtitle" | "body" | "caption" | "overline";
3445
3491
  export const TEXT_ROLES: readonly TextRole[];
3446
3492
  export const TEXT_TRANSFORMS: readonly string[];
package/dist/index.js CHANGED
@@ -257,6 +257,8 @@ export {
257
257
  } from "./button-style.js";
258
258
  // The themed text-entry field; follows the theme's `muted` scope.
259
259
  export { TextField } from "./text-field.js";
260
+ // Themed collapsible sections, drawn from the `card` and `list` scopes.
261
+ export { Accordion, AccordionItem } from "./accordion.js";
260
262
  // A themed tab strip; follows the theme's `tabs` scope.
261
263
  export { Tabs } from "./tabs.js";
262
264
  // The semantic text tiers, shared with the built-in widgets.
@@ -250,6 +250,8 @@ export {
250
250
  } from "./button-style.js";
251
251
  // The themed text-entry field; follows the theme's `muted` scope.
252
252
  export { TextField } from "./text-field.native.js";
253
+ // Themed collapsible sections, drawn from the `card` and `list` scopes.
254
+ export { Accordion, AccordionItem } from "./accordion.native.js";
253
255
  // A themed tab strip; follows the theme's `tabs` scope.
254
256
  export { Tabs } from "./tabs.native.js";
255
257
  // The semantic text tiers, shared with the built-in widgets.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colixsystems/widget-sdk",
3
- "version": "0.201.0",
3
+ "version": "0.202.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
@@ -1450,7 +1450,7 @@ const HOOKS = [
1450
1450
  message: "string | null",
1451
1451
  signerName: "string | null",
1452
1452
  signedAt: "string | null",
1453
- verdict: "{ valid, checks, content_status, ... } | null",
1453
+ verdict: "{ valid, checks, content_status, signed_by_others_since, seal, ... } | null",
1454
1454
  loading: "boolean",
1455
1455
  error: "PermissionError | null // { code, message, retryable }",
1456
1456
  initiate: "() => Promise<{ signature_id, qr, auto_start_token, status }>",
@@ -2804,6 +2804,20 @@ const PRIMITIVES = [
2804
2804
  rnComponent: null,
2805
2805
  docsUrl: null,
2806
2806
  },
2807
+ {
2808
+ name: "Accordion",
2809
+ description:
2810
+ 'Themed collapsible sections. `<Accordion defaultValue="shipping"><AccordionItem value="shipping" title="Shipping" subtitle="2-4 days">…</AccordionItem><AccordionItem value="returns" title="Returns">…</AccordionItem></Accordion>`. THE way to build a section that expands and collapses (an FAQ, a settings group, a record\'s details, a group whose members show on demand): the surface takes the app\'s card look (Theme Settings > Cards: background, border, corner radius) and the headers and dividers its list look (Theme Settings > Lists: row padding, row height, divider), so every collapsible section in the app matches. Each header has a chevron, a 44px touch target, works from the keyboard and tells assistive technology whether its section is open, so never hand-roll a collapsible from `Pressable` + a chevron + a `useState`. By default one section is open at a time and pressing the open one closes it; `multiple` lets several stay open. Props: `multiple`, `value` (the open key, or an array of keys when `multiple`; makes it controlled), `defaultValue` (the same shape, uncontrolled), `onChange(next)` (the open key(s) after a press, in the same shape), `accessibilityLabel`, `style`, `testID`. Its children are `<AccordionItem>`s.',
2811
+ rnComponent: null,
2812
+ docsUrl: null,
2813
+ },
2814
+ {
2815
+ name: "AccordionItem",
2816
+ description:
2817
+ 'One section of an `<Accordion>`. `<AccordionItem value={group.id} title={group.name} subtitle={`${group.count} members`} trailing={<Button title="Delete" variant="danger" onPress={remove} />}>…</AccordionItem>`. Props: `value` (the key `value` / `defaultValue` / `onChange` name it by — give one whenever you name sections; an item may sit inside your own component or a fragment), `title`, `subtitle` (the subtitle / caption text tiers), `trailing` (a node beside the header and OUTSIDE its toggle, so a button there runs its own action without opening the section), `disabled`, `divider` (default true; the surface edge hides the rule under the last section), `children` (the body: it mounts only while the section is open, so fetch its data inside it; an `<AccordionItem>` placed in a body is its own section, never part of the outer accordion), `accessibilityLabel` (defaults to the string `title` and `subtitle`), `style`, `testID` (also tags the header `<testID>-header` and the open body `<testID>-panel`). On its own, outside an `<Accordion>`, it is an accordion of one.',
2818
+ rnComponent: null,
2819
+ docsUrl: null,
2820
+ },
2807
2821
  {
2808
2822
  name: "Tabs",
2809
2823
  description:
@@ -5318,7 +5332,10 @@ const CONTRACT = deepFreeze({
5318
5332
  // `usePageBadge(null)` now means "not known yet" and keeps the current
5319
5333
  // badge; `undefined` still clears it. Linter rules `badge-counter-export-missing`
5320
5334
  // (error) and `badge-counter-undeclared` (warning).
5321
- version: "1.160.0",
5335
+ // 1.161.0: additive (sc-10930) — `Accordion` / `AccordionItem`, themed
5336
+ // collapsible sections drawn from the `card` and `list` scopes: single
5337
+ // or multiple open, controlled or uncontrolled, `aria-expanded` headers.
5338
+ version: "1.161.0",
5322
5339
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
5323
5340
  hooks: HOOKS,
5324
5341
  primitives: PRIMITIVES,