@apotech-os/ui-sdk 0.3.0 → 0.6.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/dist/index.d.cts CHANGED
@@ -22,6 +22,12 @@ interface AppContextValue {
22
22
  pagePath: string;
23
23
  /** The active space id, when applicable. */
24
24
  spaceId?: string;
25
+ /**
26
+ * The current user's grant role on this app context, when the shell knows it.
27
+ * Settings pages use it to render read-only fields below ADMIN instead of
28
+ * letting the user discover the 403 at save time.
29
+ */
30
+ role?: 'VIEWER' | 'EDITOR' | 'ADMIN';
25
31
  }
26
32
  declare const AppContextProvider: React.Provider<AppContextValue | null>;
27
33
  /** Returns context info for the current app page (appSlug, pagePath, spaceId). */
@@ -36,6 +42,10 @@ interface AppApiClient {
36
42
  * Perform a POST request to a platform-data endpoint.
37
43
  */
38
44
  post<T>(path: string, body?: unknown): Promise<T>;
45
+ /**
46
+ * Perform a PUT request to a platform-data endpoint (e.g. write app settings).
47
+ */
48
+ put<T>(path: string, body?: unknown): Promise<T>;
39
49
  /**
40
50
  * Perform a PATCH request to a platform-data endpoint (e.g. update a record).
41
51
  */
@@ -44,6 +54,34 @@ interface AppApiClient {
44
54
  * Perform a DELETE request to a platform-data endpoint (e.g. delete a record).
45
55
  */
46
56
  delete<T>(path: string): Promise<T>;
57
+ /**
58
+ * Run one of this app's `uiInvoke` functions and get its return value back.
59
+ *
60
+ * This is the ONLY way a page can reach an integration: the action endpoint
61
+ * requires an app token and refuses a browser session. The platform mints the
62
+ * token server-side and runs the handler in-process.
63
+ *
64
+ * The function must be declared `trigger: { type: 'uiInvoke' }` in the
65
+ * manifest, and the caller must hold its `minRole` (default EDITOR).
66
+ *
67
+ * Pass `idempotencyKey` for anything that must not run twice on a double
68
+ * click — a concurrent duplicate is rejected with 409 INVOCATION_IN_FLIGHT.
69
+ *
70
+ * Throws AppInvokeError. `code === 'FUNCTION_REJECTED'` carries an app-authored
71
+ * message that is safe to show the user; every other failure is generic on
72
+ * purpose (raw provider errors can leak tokens or PII).
73
+ */
74
+ invoke<TResult, TInput = Record<string, unknown>>(functionName: string, input?: TInput, opts?: {
75
+ idempotencyKey?: string;
76
+ signal?: AbortSignal;
77
+ }): Promise<TResult>;
78
+ }
79
+ /** Thrown by AppApiClient.invoke. */
80
+ declare class AppInvokeError extends Error {
81
+ readonly status: number;
82
+ readonly code?: string | undefined;
83
+ readonly invocationId?: string | undefined;
84
+ constructor(status: number, message: string, code?: string | undefined, invocationId?: string | undefined);
47
85
  }
48
86
  declare const AppApiProvider: React.Provider<AppApiClient | null>;
49
87
  /**
@@ -51,6 +89,26 @@ declare const AppApiProvider: React.Provider<AppApiClient | null>;
51
89
  * Requests go to /api/data/* with the session cookie (RBAC applies server-side).
52
90
  */
53
91
  declare function useAppApi(): AppApiClient;
92
+ /**
93
+ * Reads and writes this app's settings (GET/PUT /data/apps/:appSlug/settings).
94
+ *
95
+ * The app slug comes from the surrounding AppContext, so a settings page only
96
+ * has to describe its form. Values are the ones the server merged with the
97
+ * `settings.fields` defaults declared in the manifest.
98
+ *
99
+ * Reading requires VIEWER, writing requires ADMIN. `save` never throws: it
100
+ * reports through `saveError`, which carries the raw failure (422
101
+ * SETTINGS_VALIDATION_FAILED with its per-key errors, or 403) so the page can
102
+ * map it back onto its fields. A failed save leaves `settings` untouched.
103
+ */
104
+ declare function useAppSettings<T = Record<string, unknown>>(): {
105
+ settings: T | undefined;
106
+ isLoading: boolean;
107
+ error: unknown;
108
+ save: (patch: Partial<T>) => Promise<void>;
109
+ isSaving: boolean;
110
+ saveError: unknown;
111
+ };
54
112
 
55
113
  declare const buttonVariants: (props?: ({
56
114
  variant?: "default" | "secondary" | "outline" | "ghost" | "destructive" | null | undefined;
@@ -139,5 +197,101 @@ interface DateRangePickerProps {
139
197
  }
140
198
  /** Minimal controlled date-range picker using two native date inputs. */
141
199
  declare function DateRangePicker({ value, onChange, className, disabled }: DateRangePickerProps): React.JSX.Element;
200
+ type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;
201
+ declare const Textarea: React.ForwardRefExoticComponent<TextareaProps & React.RefAttributes<HTMLTextAreaElement>>;
202
+ interface CheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type' | 'onChange'> {
203
+ checked?: boolean;
204
+ /** Preferred over onChange — receives the boolean directly. */
205
+ onCheckedChange?: (checked: boolean) => void;
206
+ label?: React.ReactNode;
207
+ }
208
+ declare const Checkbox: React.ForwardRefExoticComponent<CheckboxProps & React.RefAttributes<HTMLInputElement>>;
209
+ interface SidePanelProps {
210
+ open: boolean;
211
+ onClose: () => void;
212
+ title?: React.ReactNode;
213
+ description?: React.ReactNode;
214
+ children: React.ReactNode;
215
+ className?: string;
216
+ }
217
+ /**
218
+ * Blocking slide-over — reach for it when a task has more fields than a `Modal`
219
+ * can show without an inner scrollbar. Full height, so the body scrolls once and
220
+ * a footer can stay pinned; `Modal` centres a box and does no height management.
221
+ *
222
+ * Children are laid out in a flex column filling the panel: put the scrollable
223
+ * region in a `min-h-0 flex-1 overflow-y-auto` child and the footer after it.
224
+ *
225
+ * NOTE: no focus management. Autofocus the primary action yourself.
226
+ */
227
+ declare function SidePanel({ open, onClose, title, description, children, className, }: SidePanelProps): React.JSX.Element | null;
228
+ interface CopyButtonProps {
229
+ value: string;
230
+ label?: string;
231
+ copiedLabel?: string;
232
+ variant?: ButtonProps['variant'];
233
+ size?: ButtonProps['size'];
234
+ className?: string;
235
+ }
236
+ /**
237
+ * Copy-to-clipboard button with inline feedback. On failure it says so rather
238
+ * than silently doing nothing — keep the copyable text selectable so manual
239
+ * copy stays the fallback.
240
+ */
241
+ declare function CopyButton({ value, label, copiedLabel, variant, size, className, }: CopyButtonProps): React.JSX.Element;
242
+ interface PopoverProps {
243
+ open: boolean;
244
+ onOpenChange: (open: boolean) => void;
245
+ trigger: React.ReactNode;
246
+ children: React.ReactNode;
247
+ align?: 'start' | 'end';
248
+ className?: string;
249
+ }
250
+ /** Anchored panel. Controlled — the caller owns `open`. Closes on outside click
251
+ * and Escape, which is what a filter pill needs and a Modal is too heavy for. */
252
+ declare function Popover({ open, onOpenChange, trigger, children, align, className, }: PopoverProps): React.JSX.Element;
253
+ interface DropdownMenuItem {
254
+ label: React.ReactNode;
255
+ onSelect: () => void;
256
+ disabled?: boolean;
257
+ destructive?: boolean;
258
+ }
259
+ interface DropdownMenuProps {
260
+ trigger: React.ReactNode;
261
+ items: DropdownMenuItem[];
262
+ align?: 'start' | 'end';
263
+ className?: string;
264
+ }
265
+ /** Uncontrolled menu built on Popover. Selecting an item closes it. */
266
+ declare function DropdownMenu({ trigger, items, align, className }: DropdownMenuProps): React.JSX.Element;
267
+
268
+ /**
269
+ * Browser helpers for app pages.
270
+ *
271
+ * App bundles get react, react-dom and this SDK from the shell's import map and
272
+ * bundle everything else themselves — so without these here, every app would
273
+ * reimplement clipboard handling and CSV escaping, and get the edge cases wrong
274
+ * in its own way.
275
+ */
276
+ /**
277
+ * Copy text to the clipboard.
278
+ *
279
+ * Returns `false` instead of throwing when the clipboard is unavailable (non
280
+ * secure context, permission refused) so the caller can render inline feedback.
281
+ * No `document.execCommand('copy')` fallback: it is deprecated, and the shell
282
+ * only ever runs on https or localhost, both secure contexts.
283
+ */
284
+ declare function copyToClipboard(text: string): Promise<boolean>;
285
+ /** Trigger a browser download of a Blob via a throwaway object URL + `<a>` click. */
286
+ declare function downloadBlob(blob: Blob, filename: string): void;
287
+ /** Serialize rows (header included) into an RFC 4180 CSV string. */
288
+ declare function toCsv(rows: string[][]): string;
289
+ /**
290
+ * Serialize + download in one call.
291
+ *
292
+ * The BOM is the whole reason this helper exists: without it Excel on Windows
293
+ * reads the file as latin-1 and mangles every accented character.
294
+ */
295
+ declare function downloadCsv(rows: string[][], filename: string): void;
142
296
 
143
- export { type AppApiClient, AppApiProvider, AppContextProvider, type AppContextValue, Badge, type BadgeProps, Button, type ButtonProps, Card, CardContent, CardHeader, CardTitle, type DateRange, DateRangePicker, type DateRangePickerProps, Input, Link, type LinkProps, Modal, type ModalProps, Select, type SelectProps, Spinner, type SpinnerProps, Switch, type SwitchProps, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, type TabsProps, TabsTrigger, cn, useAppApi, useAppContext };
297
+ export { type AppApiClient, AppApiProvider, AppContextProvider, type AppContextValue, AppInvokeError, Badge, type BadgeProps, Button, type ButtonProps, Card, CardContent, CardHeader, CardTitle, Checkbox, type CheckboxProps, CopyButton, type CopyButtonProps, type DateRange, DateRangePicker, type DateRangePickerProps, DropdownMenu, type DropdownMenuItem, type DropdownMenuProps, Input, Link, type LinkProps, Modal, type ModalProps, Popover, type PopoverProps, Select, type SelectProps, SidePanel, type SidePanelProps, Spinner, type SpinnerProps, Switch, type SwitchProps, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, type TabsProps, TabsTrigger, Textarea, type TextareaProps, cn, copyToClipboard, downloadBlob, downloadCsv, toCsv, useAppApi, useAppContext, useAppSettings };
package/dist/index.d.ts CHANGED
@@ -22,6 +22,12 @@ interface AppContextValue {
22
22
  pagePath: string;
23
23
  /** The active space id, when applicable. */
24
24
  spaceId?: string;
25
+ /**
26
+ * The current user's grant role on this app context, when the shell knows it.
27
+ * Settings pages use it to render read-only fields below ADMIN instead of
28
+ * letting the user discover the 403 at save time.
29
+ */
30
+ role?: 'VIEWER' | 'EDITOR' | 'ADMIN';
25
31
  }
26
32
  declare const AppContextProvider: React.Provider<AppContextValue | null>;
27
33
  /** Returns context info for the current app page (appSlug, pagePath, spaceId). */
@@ -36,6 +42,10 @@ interface AppApiClient {
36
42
  * Perform a POST request to a platform-data endpoint.
37
43
  */
38
44
  post<T>(path: string, body?: unknown): Promise<T>;
45
+ /**
46
+ * Perform a PUT request to a platform-data endpoint (e.g. write app settings).
47
+ */
48
+ put<T>(path: string, body?: unknown): Promise<T>;
39
49
  /**
40
50
  * Perform a PATCH request to a platform-data endpoint (e.g. update a record).
41
51
  */
@@ -44,6 +54,34 @@ interface AppApiClient {
44
54
  * Perform a DELETE request to a platform-data endpoint (e.g. delete a record).
45
55
  */
46
56
  delete<T>(path: string): Promise<T>;
57
+ /**
58
+ * Run one of this app's `uiInvoke` functions and get its return value back.
59
+ *
60
+ * This is the ONLY way a page can reach an integration: the action endpoint
61
+ * requires an app token and refuses a browser session. The platform mints the
62
+ * token server-side and runs the handler in-process.
63
+ *
64
+ * The function must be declared `trigger: { type: 'uiInvoke' }` in the
65
+ * manifest, and the caller must hold its `minRole` (default EDITOR).
66
+ *
67
+ * Pass `idempotencyKey` for anything that must not run twice on a double
68
+ * click — a concurrent duplicate is rejected with 409 INVOCATION_IN_FLIGHT.
69
+ *
70
+ * Throws AppInvokeError. `code === 'FUNCTION_REJECTED'` carries an app-authored
71
+ * message that is safe to show the user; every other failure is generic on
72
+ * purpose (raw provider errors can leak tokens or PII).
73
+ */
74
+ invoke<TResult, TInput = Record<string, unknown>>(functionName: string, input?: TInput, opts?: {
75
+ idempotencyKey?: string;
76
+ signal?: AbortSignal;
77
+ }): Promise<TResult>;
78
+ }
79
+ /** Thrown by AppApiClient.invoke. */
80
+ declare class AppInvokeError extends Error {
81
+ readonly status: number;
82
+ readonly code?: string | undefined;
83
+ readonly invocationId?: string | undefined;
84
+ constructor(status: number, message: string, code?: string | undefined, invocationId?: string | undefined);
47
85
  }
48
86
  declare const AppApiProvider: React.Provider<AppApiClient | null>;
49
87
  /**
@@ -51,6 +89,26 @@ declare const AppApiProvider: React.Provider<AppApiClient | null>;
51
89
  * Requests go to /api/data/* with the session cookie (RBAC applies server-side).
52
90
  */
53
91
  declare function useAppApi(): AppApiClient;
92
+ /**
93
+ * Reads and writes this app's settings (GET/PUT /data/apps/:appSlug/settings).
94
+ *
95
+ * The app slug comes from the surrounding AppContext, so a settings page only
96
+ * has to describe its form. Values are the ones the server merged with the
97
+ * `settings.fields` defaults declared in the manifest.
98
+ *
99
+ * Reading requires VIEWER, writing requires ADMIN. `save` never throws: it
100
+ * reports through `saveError`, which carries the raw failure (422
101
+ * SETTINGS_VALIDATION_FAILED with its per-key errors, or 403) so the page can
102
+ * map it back onto its fields. A failed save leaves `settings` untouched.
103
+ */
104
+ declare function useAppSettings<T = Record<string, unknown>>(): {
105
+ settings: T | undefined;
106
+ isLoading: boolean;
107
+ error: unknown;
108
+ save: (patch: Partial<T>) => Promise<void>;
109
+ isSaving: boolean;
110
+ saveError: unknown;
111
+ };
54
112
 
55
113
  declare const buttonVariants: (props?: ({
56
114
  variant?: "default" | "secondary" | "outline" | "ghost" | "destructive" | null | undefined;
@@ -139,5 +197,101 @@ interface DateRangePickerProps {
139
197
  }
140
198
  /** Minimal controlled date-range picker using two native date inputs. */
141
199
  declare function DateRangePicker({ value, onChange, className, disabled }: DateRangePickerProps): React.JSX.Element;
200
+ type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;
201
+ declare const Textarea: React.ForwardRefExoticComponent<TextareaProps & React.RefAttributes<HTMLTextAreaElement>>;
202
+ interface CheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type' | 'onChange'> {
203
+ checked?: boolean;
204
+ /** Preferred over onChange — receives the boolean directly. */
205
+ onCheckedChange?: (checked: boolean) => void;
206
+ label?: React.ReactNode;
207
+ }
208
+ declare const Checkbox: React.ForwardRefExoticComponent<CheckboxProps & React.RefAttributes<HTMLInputElement>>;
209
+ interface SidePanelProps {
210
+ open: boolean;
211
+ onClose: () => void;
212
+ title?: React.ReactNode;
213
+ description?: React.ReactNode;
214
+ children: React.ReactNode;
215
+ className?: string;
216
+ }
217
+ /**
218
+ * Blocking slide-over — reach for it when a task has more fields than a `Modal`
219
+ * can show without an inner scrollbar. Full height, so the body scrolls once and
220
+ * a footer can stay pinned; `Modal` centres a box and does no height management.
221
+ *
222
+ * Children are laid out in a flex column filling the panel: put the scrollable
223
+ * region in a `min-h-0 flex-1 overflow-y-auto` child and the footer after it.
224
+ *
225
+ * NOTE: no focus management. Autofocus the primary action yourself.
226
+ */
227
+ declare function SidePanel({ open, onClose, title, description, children, className, }: SidePanelProps): React.JSX.Element | null;
228
+ interface CopyButtonProps {
229
+ value: string;
230
+ label?: string;
231
+ copiedLabel?: string;
232
+ variant?: ButtonProps['variant'];
233
+ size?: ButtonProps['size'];
234
+ className?: string;
235
+ }
236
+ /**
237
+ * Copy-to-clipboard button with inline feedback. On failure it says so rather
238
+ * than silently doing nothing — keep the copyable text selectable so manual
239
+ * copy stays the fallback.
240
+ */
241
+ declare function CopyButton({ value, label, copiedLabel, variant, size, className, }: CopyButtonProps): React.JSX.Element;
242
+ interface PopoverProps {
243
+ open: boolean;
244
+ onOpenChange: (open: boolean) => void;
245
+ trigger: React.ReactNode;
246
+ children: React.ReactNode;
247
+ align?: 'start' | 'end';
248
+ className?: string;
249
+ }
250
+ /** Anchored panel. Controlled — the caller owns `open`. Closes on outside click
251
+ * and Escape, which is what a filter pill needs and a Modal is too heavy for. */
252
+ declare function Popover({ open, onOpenChange, trigger, children, align, className, }: PopoverProps): React.JSX.Element;
253
+ interface DropdownMenuItem {
254
+ label: React.ReactNode;
255
+ onSelect: () => void;
256
+ disabled?: boolean;
257
+ destructive?: boolean;
258
+ }
259
+ interface DropdownMenuProps {
260
+ trigger: React.ReactNode;
261
+ items: DropdownMenuItem[];
262
+ align?: 'start' | 'end';
263
+ className?: string;
264
+ }
265
+ /** Uncontrolled menu built on Popover. Selecting an item closes it. */
266
+ declare function DropdownMenu({ trigger, items, align, className }: DropdownMenuProps): React.JSX.Element;
267
+
268
+ /**
269
+ * Browser helpers for app pages.
270
+ *
271
+ * App bundles get react, react-dom and this SDK from the shell's import map and
272
+ * bundle everything else themselves — so without these here, every app would
273
+ * reimplement clipboard handling and CSV escaping, and get the edge cases wrong
274
+ * in its own way.
275
+ */
276
+ /**
277
+ * Copy text to the clipboard.
278
+ *
279
+ * Returns `false` instead of throwing when the clipboard is unavailable (non
280
+ * secure context, permission refused) so the caller can render inline feedback.
281
+ * No `document.execCommand('copy')` fallback: it is deprecated, and the shell
282
+ * only ever runs on https or localhost, both secure contexts.
283
+ */
284
+ declare function copyToClipboard(text: string): Promise<boolean>;
285
+ /** Trigger a browser download of a Blob via a throwaway object URL + `<a>` click. */
286
+ declare function downloadBlob(blob: Blob, filename: string): void;
287
+ /** Serialize rows (header included) into an RFC 4180 CSV string. */
288
+ declare function toCsv(rows: string[][]): string;
289
+ /**
290
+ * Serialize + download in one call.
291
+ *
292
+ * The BOM is the whole reason this helper exists: without it Excel on Windows
293
+ * reads the file as latin-1 and mangles every accented character.
294
+ */
295
+ declare function downloadCsv(rows: string[][], filename: string): void;
142
296
 
143
- export { type AppApiClient, AppApiProvider, AppContextProvider, type AppContextValue, Badge, type BadgeProps, Button, type ButtonProps, Card, CardContent, CardHeader, CardTitle, type DateRange, DateRangePicker, type DateRangePickerProps, Input, Link, type LinkProps, Modal, type ModalProps, Select, type SelectProps, Spinner, type SpinnerProps, Switch, type SwitchProps, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, type TabsProps, TabsTrigger, cn, useAppApi, useAppContext };
297
+ export { type AppApiClient, AppApiProvider, AppContextProvider, type AppContextValue, AppInvokeError, Badge, type BadgeProps, Button, type ButtonProps, Card, CardContent, CardHeader, CardTitle, Checkbox, type CheckboxProps, CopyButton, type CopyButtonProps, type DateRange, DateRangePicker, type DateRangePickerProps, DropdownMenu, type DropdownMenuItem, type DropdownMenuProps, Input, Link, type LinkProps, Modal, type ModalProps, Popover, type PopoverProps, Select, type SelectProps, SidePanel, type SidePanelProps, Spinner, type SpinnerProps, Switch, type SwitchProps, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, type TabsProps, TabsTrigger, Textarea, type TextareaProps, cn, copyToClipboard, downloadBlob, downloadCsv, toCsv, useAppApi, useAppContext, useAppSettings };