@apotech-os/ui-sdk 0.3.0 → 0.5.0-rc.1
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.cjs +304 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +125 -1
- package/dist/index.d.ts +125 -1
- package/dist/index.js +294 -1
- package/dist/index.js.map +1 -1
- package/package.json +6 -1
package/dist/index.d.cts
CHANGED
|
@@ -44,6 +44,34 @@ interface AppApiClient {
|
|
|
44
44
|
* Perform a DELETE request to a platform-data endpoint (e.g. delete a record).
|
|
45
45
|
*/
|
|
46
46
|
delete<T>(path: string): Promise<T>;
|
|
47
|
+
/**
|
|
48
|
+
* Run one of this app's `uiInvoke` functions and get its return value back.
|
|
49
|
+
*
|
|
50
|
+
* This is the ONLY way a page can reach an integration: the action endpoint
|
|
51
|
+
* requires an app token and refuses a browser session. The platform mints the
|
|
52
|
+
* token server-side and runs the handler in-process.
|
|
53
|
+
*
|
|
54
|
+
* The function must be declared `trigger: { type: 'uiInvoke' }` in the
|
|
55
|
+
* manifest, and the caller must hold its `minRole` (default EDITOR).
|
|
56
|
+
*
|
|
57
|
+
* Pass `idempotencyKey` for anything that must not run twice on a double
|
|
58
|
+
* click — a concurrent duplicate is rejected with 409 INVOCATION_IN_FLIGHT.
|
|
59
|
+
*
|
|
60
|
+
* Throws AppInvokeError. `code === 'FUNCTION_REJECTED'` carries an app-authored
|
|
61
|
+
* message that is safe to show the user; every other failure is generic on
|
|
62
|
+
* purpose (raw provider errors can leak tokens or PII).
|
|
63
|
+
*/
|
|
64
|
+
invoke<TResult, TInput = Record<string, unknown>>(functionName: string, input?: TInput, opts?: {
|
|
65
|
+
idempotencyKey?: string;
|
|
66
|
+
signal?: AbortSignal;
|
|
67
|
+
}): Promise<TResult>;
|
|
68
|
+
}
|
|
69
|
+
/** Thrown by AppApiClient.invoke. */
|
|
70
|
+
declare class AppInvokeError extends Error {
|
|
71
|
+
readonly status: number;
|
|
72
|
+
readonly code?: string | undefined;
|
|
73
|
+
readonly invocationId?: string | undefined;
|
|
74
|
+
constructor(status: number, message: string, code?: string | undefined, invocationId?: string | undefined);
|
|
47
75
|
}
|
|
48
76
|
declare const AppApiProvider: React.Provider<AppApiClient | null>;
|
|
49
77
|
/**
|
|
@@ -139,5 +167,101 @@ interface DateRangePickerProps {
|
|
|
139
167
|
}
|
|
140
168
|
/** Minimal controlled date-range picker using two native date inputs. */
|
|
141
169
|
declare function DateRangePicker({ value, onChange, className, disabled }: DateRangePickerProps): React.JSX.Element;
|
|
170
|
+
type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;
|
|
171
|
+
declare const Textarea: React.ForwardRefExoticComponent<TextareaProps & React.RefAttributes<HTMLTextAreaElement>>;
|
|
172
|
+
interface CheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type' | 'onChange'> {
|
|
173
|
+
checked?: boolean;
|
|
174
|
+
/** Preferred over onChange — receives the boolean directly. */
|
|
175
|
+
onCheckedChange?: (checked: boolean) => void;
|
|
176
|
+
label?: React.ReactNode;
|
|
177
|
+
}
|
|
178
|
+
declare const Checkbox: React.ForwardRefExoticComponent<CheckboxProps & React.RefAttributes<HTMLInputElement>>;
|
|
179
|
+
interface SidePanelProps {
|
|
180
|
+
open: boolean;
|
|
181
|
+
onClose: () => void;
|
|
182
|
+
title?: React.ReactNode;
|
|
183
|
+
description?: React.ReactNode;
|
|
184
|
+
children: React.ReactNode;
|
|
185
|
+
className?: string;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Blocking slide-over — reach for it when a task has more fields than a `Modal`
|
|
189
|
+
* can show without an inner scrollbar. Full height, so the body scrolls once and
|
|
190
|
+
* a footer can stay pinned; `Modal` centres a box and does no height management.
|
|
191
|
+
*
|
|
192
|
+
* Children are laid out in a flex column filling the panel: put the scrollable
|
|
193
|
+
* region in a `min-h-0 flex-1 overflow-y-auto` child and the footer after it.
|
|
194
|
+
*
|
|
195
|
+
* NOTE: no focus management. Autofocus the primary action yourself.
|
|
196
|
+
*/
|
|
197
|
+
declare function SidePanel({ open, onClose, title, description, children, className, }: SidePanelProps): React.JSX.Element | null;
|
|
198
|
+
interface CopyButtonProps {
|
|
199
|
+
value: string;
|
|
200
|
+
label?: string;
|
|
201
|
+
copiedLabel?: string;
|
|
202
|
+
variant?: ButtonProps['variant'];
|
|
203
|
+
size?: ButtonProps['size'];
|
|
204
|
+
className?: string;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Copy-to-clipboard button with inline feedback. On failure it says so rather
|
|
208
|
+
* than silently doing nothing — keep the copyable text selectable so manual
|
|
209
|
+
* copy stays the fallback.
|
|
210
|
+
*/
|
|
211
|
+
declare function CopyButton({ value, label, copiedLabel, variant, size, className, }: CopyButtonProps): React.JSX.Element;
|
|
212
|
+
interface PopoverProps {
|
|
213
|
+
open: boolean;
|
|
214
|
+
onOpenChange: (open: boolean) => void;
|
|
215
|
+
trigger: React.ReactNode;
|
|
216
|
+
children: React.ReactNode;
|
|
217
|
+
align?: 'start' | 'end';
|
|
218
|
+
className?: string;
|
|
219
|
+
}
|
|
220
|
+
/** Anchored panel. Controlled — the caller owns `open`. Closes on outside click
|
|
221
|
+
* and Escape, which is what a filter pill needs and a Modal is too heavy for. */
|
|
222
|
+
declare function Popover({ open, onOpenChange, trigger, children, align, className, }: PopoverProps): React.JSX.Element;
|
|
223
|
+
interface DropdownMenuItem {
|
|
224
|
+
label: React.ReactNode;
|
|
225
|
+
onSelect: () => void;
|
|
226
|
+
disabled?: boolean;
|
|
227
|
+
destructive?: boolean;
|
|
228
|
+
}
|
|
229
|
+
interface DropdownMenuProps {
|
|
230
|
+
trigger: React.ReactNode;
|
|
231
|
+
items: DropdownMenuItem[];
|
|
232
|
+
align?: 'start' | 'end';
|
|
233
|
+
className?: string;
|
|
234
|
+
}
|
|
235
|
+
/** Uncontrolled menu built on Popover. Selecting an item closes it. */
|
|
236
|
+
declare function DropdownMenu({ trigger, items, align, className }: DropdownMenuProps): React.JSX.Element;
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Browser helpers for app pages.
|
|
240
|
+
*
|
|
241
|
+
* App bundles get react, react-dom and this SDK from the shell's import map and
|
|
242
|
+
* bundle everything else themselves — so without these here, every app would
|
|
243
|
+
* reimplement clipboard handling and CSV escaping, and get the edge cases wrong
|
|
244
|
+
* in its own way.
|
|
245
|
+
*/
|
|
246
|
+
/**
|
|
247
|
+
* Copy text to the clipboard.
|
|
248
|
+
*
|
|
249
|
+
* Returns `false` instead of throwing when the clipboard is unavailable (non
|
|
250
|
+
* secure context, permission refused) so the caller can render inline feedback.
|
|
251
|
+
* No `document.execCommand('copy')` fallback: it is deprecated, and the shell
|
|
252
|
+
* only ever runs on https or localhost, both secure contexts.
|
|
253
|
+
*/
|
|
254
|
+
declare function copyToClipboard(text: string): Promise<boolean>;
|
|
255
|
+
/** Trigger a browser download of a Blob via a throwaway object URL + `<a>` click. */
|
|
256
|
+
declare function downloadBlob(blob: Blob, filename: string): void;
|
|
257
|
+
/** Serialize rows (header included) into an RFC 4180 CSV string. */
|
|
258
|
+
declare function toCsv(rows: string[][]): string;
|
|
259
|
+
/**
|
|
260
|
+
* Serialize + download in one call.
|
|
261
|
+
*
|
|
262
|
+
* The BOM is the whole reason this helper exists: without it Excel on Windows
|
|
263
|
+
* reads the file as latin-1 and mangles every accented character.
|
|
264
|
+
*/
|
|
265
|
+
declare function downloadCsv(rows: string[][], filename: string): void;
|
|
142
266
|
|
|
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 };
|
|
267
|
+
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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -44,6 +44,34 @@ interface AppApiClient {
|
|
|
44
44
|
* Perform a DELETE request to a platform-data endpoint (e.g. delete a record).
|
|
45
45
|
*/
|
|
46
46
|
delete<T>(path: string): Promise<T>;
|
|
47
|
+
/**
|
|
48
|
+
* Run one of this app's `uiInvoke` functions and get its return value back.
|
|
49
|
+
*
|
|
50
|
+
* This is the ONLY way a page can reach an integration: the action endpoint
|
|
51
|
+
* requires an app token and refuses a browser session. The platform mints the
|
|
52
|
+
* token server-side and runs the handler in-process.
|
|
53
|
+
*
|
|
54
|
+
* The function must be declared `trigger: { type: 'uiInvoke' }` in the
|
|
55
|
+
* manifest, and the caller must hold its `minRole` (default EDITOR).
|
|
56
|
+
*
|
|
57
|
+
* Pass `idempotencyKey` for anything that must not run twice on a double
|
|
58
|
+
* click — a concurrent duplicate is rejected with 409 INVOCATION_IN_FLIGHT.
|
|
59
|
+
*
|
|
60
|
+
* Throws AppInvokeError. `code === 'FUNCTION_REJECTED'` carries an app-authored
|
|
61
|
+
* message that is safe to show the user; every other failure is generic on
|
|
62
|
+
* purpose (raw provider errors can leak tokens or PII).
|
|
63
|
+
*/
|
|
64
|
+
invoke<TResult, TInput = Record<string, unknown>>(functionName: string, input?: TInput, opts?: {
|
|
65
|
+
idempotencyKey?: string;
|
|
66
|
+
signal?: AbortSignal;
|
|
67
|
+
}): Promise<TResult>;
|
|
68
|
+
}
|
|
69
|
+
/** Thrown by AppApiClient.invoke. */
|
|
70
|
+
declare class AppInvokeError extends Error {
|
|
71
|
+
readonly status: number;
|
|
72
|
+
readonly code?: string | undefined;
|
|
73
|
+
readonly invocationId?: string | undefined;
|
|
74
|
+
constructor(status: number, message: string, code?: string | undefined, invocationId?: string | undefined);
|
|
47
75
|
}
|
|
48
76
|
declare const AppApiProvider: React.Provider<AppApiClient | null>;
|
|
49
77
|
/**
|
|
@@ -139,5 +167,101 @@ interface DateRangePickerProps {
|
|
|
139
167
|
}
|
|
140
168
|
/** Minimal controlled date-range picker using two native date inputs. */
|
|
141
169
|
declare function DateRangePicker({ value, onChange, className, disabled }: DateRangePickerProps): React.JSX.Element;
|
|
170
|
+
type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;
|
|
171
|
+
declare const Textarea: React.ForwardRefExoticComponent<TextareaProps & React.RefAttributes<HTMLTextAreaElement>>;
|
|
172
|
+
interface CheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type' | 'onChange'> {
|
|
173
|
+
checked?: boolean;
|
|
174
|
+
/** Preferred over onChange — receives the boolean directly. */
|
|
175
|
+
onCheckedChange?: (checked: boolean) => void;
|
|
176
|
+
label?: React.ReactNode;
|
|
177
|
+
}
|
|
178
|
+
declare const Checkbox: React.ForwardRefExoticComponent<CheckboxProps & React.RefAttributes<HTMLInputElement>>;
|
|
179
|
+
interface SidePanelProps {
|
|
180
|
+
open: boolean;
|
|
181
|
+
onClose: () => void;
|
|
182
|
+
title?: React.ReactNode;
|
|
183
|
+
description?: React.ReactNode;
|
|
184
|
+
children: React.ReactNode;
|
|
185
|
+
className?: string;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Blocking slide-over — reach for it when a task has more fields than a `Modal`
|
|
189
|
+
* can show without an inner scrollbar. Full height, so the body scrolls once and
|
|
190
|
+
* a footer can stay pinned; `Modal` centres a box and does no height management.
|
|
191
|
+
*
|
|
192
|
+
* Children are laid out in a flex column filling the panel: put the scrollable
|
|
193
|
+
* region in a `min-h-0 flex-1 overflow-y-auto` child and the footer after it.
|
|
194
|
+
*
|
|
195
|
+
* NOTE: no focus management. Autofocus the primary action yourself.
|
|
196
|
+
*/
|
|
197
|
+
declare function SidePanel({ open, onClose, title, description, children, className, }: SidePanelProps): React.JSX.Element | null;
|
|
198
|
+
interface CopyButtonProps {
|
|
199
|
+
value: string;
|
|
200
|
+
label?: string;
|
|
201
|
+
copiedLabel?: string;
|
|
202
|
+
variant?: ButtonProps['variant'];
|
|
203
|
+
size?: ButtonProps['size'];
|
|
204
|
+
className?: string;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Copy-to-clipboard button with inline feedback. On failure it says so rather
|
|
208
|
+
* than silently doing nothing — keep the copyable text selectable so manual
|
|
209
|
+
* copy stays the fallback.
|
|
210
|
+
*/
|
|
211
|
+
declare function CopyButton({ value, label, copiedLabel, variant, size, className, }: CopyButtonProps): React.JSX.Element;
|
|
212
|
+
interface PopoverProps {
|
|
213
|
+
open: boolean;
|
|
214
|
+
onOpenChange: (open: boolean) => void;
|
|
215
|
+
trigger: React.ReactNode;
|
|
216
|
+
children: React.ReactNode;
|
|
217
|
+
align?: 'start' | 'end';
|
|
218
|
+
className?: string;
|
|
219
|
+
}
|
|
220
|
+
/** Anchored panel. Controlled — the caller owns `open`. Closes on outside click
|
|
221
|
+
* and Escape, which is what a filter pill needs and a Modal is too heavy for. */
|
|
222
|
+
declare function Popover({ open, onOpenChange, trigger, children, align, className, }: PopoverProps): React.JSX.Element;
|
|
223
|
+
interface DropdownMenuItem {
|
|
224
|
+
label: React.ReactNode;
|
|
225
|
+
onSelect: () => void;
|
|
226
|
+
disabled?: boolean;
|
|
227
|
+
destructive?: boolean;
|
|
228
|
+
}
|
|
229
|
+
interface DropdownMenuProps {
|
|
230
|
+
trigger: React.ReactNode;
|
|
231
|
+
items: DropdownMenuItem[];
|
|
232
|
+
align?: 'start' | 'end';
|
|
233
|
+
className?: string;
|
|
234
|
+
}
|
|
235
|
+
/** Uncontrolled menu built on Popover. Selecting an item closes it. */
|
|
236
|
+
declare function DropdownMenu({ trigger, items, align, className }: DropdownMenuProps): React.JSX.Element;
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Browser helpers for app pages.
|
|
240
|
+
*
|
|
241
|
+
* App bundles get react, react-dom and this SDK from the shell's import map and
|
|
242
|
+
* bundle everything else themselves — so without these here, every app would
|
|
243
|
+
* reimplement clipboard handling and CSV escaping, and get the edge cases wrong
|
|
244
|
+
* in its own way.
|
|
245
|
+
*/
|
|
246
|
+
/**
|
|
247
|
+
* Copy text to the clipboard.
|
|
248
|
+
*
|
|
249
|
+
* Returns `false` instead of throwing when the clipboard is unavailable (non
|
|
250
|
+
* secure context, permission refused) so the caller can render inline feedback.
|
|
251
|
+
* No `document.execCommand('copy')` fallback: it is deprecated, and the shell
|
|
252
|
+
* only ever runs on https or localhost, both secure contexts.
|
|
253
|
+
*/
|
|
254
|
+
declare function copyToClipboard(text: string): Promise<boolean>;
|
|
255
|
+
/** Trigger a browser download of a Blob via a throwaway object URL + `<a>` click. */
|
|
256
|
+
declare function downloadBlob(blob: Blob, filename: string): void;
|
|
257
|
+
/** Serialize rows (header included) into an RFC 4180 CSV string. */
|
|
258
|
+
declare function toCsv(rows: string[][]): string;
|
|
259
|
+
/**
|
|
260
|
+
* Serialize + download in one call.
|
|
261
|
+
*
|
|
262
|
+
* The BOM is the whole reason this helper exists: without it Excel on Windows
|
|
263
|
+
* reads the file as latin-1 and mangles every accented character.
|
|
264
|
+
*/
|
|
265
|
+
declare function downloadCsv(rows: string[][], filename: string): void;
|
|
142
266
|
|
|
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 };
|
|
267
|
+
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 };
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,15 @@ function useAppContext() {
|
|
|
16
16
|
if (!ctx) throw new Error("useAppContext must be used inside an app page mounted by the shell");
|
|
17
17
|
return ctx;
|
|
18
18
|
}
|
|
19
|
+
var AppInvokeError = class extends Error {
|
|
20
|
+
constructor(status, message, code, invocationId) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.status = status;
|
|
23
|
+
this.code = code;
|
|
24
|
+
this.invocationId = invocationId;
|
|
25
|
+
this.name = "AppInvokeError";
|
|
26
|
+
}
|
|
27
|
+
};
|
|
19
28
|
var AppApiCtx = createContext(null);
|
|
20
29
|
var AppApiProvider = AppApiCtx.Provider;
|
|
21
30
|
function useAppApi() {
|
|
@@ -23,6 +32,38 @@ function useAppApi() {
|
|
|
23
32
|
if (!client) throw new Error("useAppApi must be used inside an app page mounted by the shell");
|
|
24
33
|
return client;
|
|
25
34
|
}
|
|
35
|
+
|
|
36
|
+
// src/utils.ts
|
|
37
|
+
async function copyToClipboard(text) {
|
|
38
|
+
if (!navigator?.clipboard || !window.isSecureContext) return false;
|
|
39
|
+
try {
|
|
40
|
+
await navigator.clipboard.writeText(text);
|
|
41
|
+
return true;
|
|
42
|
+
} catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function downloadBlob(blob, filename) {
|
|
47
|
+
const url = URL.createObjectURL(blob);
|
|
48
|
+
const a = document.createElement("a");
|
|
49
|
+
a.href = url;
|
|
50
|
+
a.download = filename;
|
|
51
|
+
document.body.appendChild(a);
|
|
52
|
+
a.click();
|
|
53
|
+
a.remove();
|
|
54
|
+
URL.revokeObjectURL(url);
|
|
55
|
+
}
|
|
56
|
+
function escapeCsvField(value) {
|
|
57
|
+
return /[",\r\n]/.test(value) ? `"${value.replace(/"/g, '""')}"` : value;
|
|
58
|
+
}
|
|
59
|
+
function toCsv(rows) {
|
|
60
|
+
return rows.map((row) => row.map(escapeCsvField).join(",")).join("\r\n");
|
|
61
|
+
}
|
|
62
|
+
var BOM = "\uFEFF";
|
|
63
|
+
function downloadCsv(rows, filename) {
|
|
64
|
+
const blob = new Blob([`${BOM}${toCsv(rows)}`], { type: "text/csv;charset=utf-8" });
|
|
65
|
+
downloadBlob(blob, filename);
|
|
66
|
+
}
|
|
26
67
|
var buttonVariants = cva(
|
|
27
68
|
"inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
|
|
28
69
|
{
|
|
@@ -336,7 +377,259 @@ function DateRangePicker({ value, onChange, className, disabled }) {
|
|
|
336
377
|
)
|
|
337
378
|
] });
|
|
338
379
|
}
|
|
380
|
+
function IconX({ className }) {
|
|
381
|
+
return /* @__PURE__ */ jsx(
|
|
382
|
+
"svg",
|
|
383
|
+
{
|
|
384
|
+
className,
|
|
385
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
386
|
+
viewBox: "0 0 24 24",
|
|
387
|
+
fill: "none",
|
|
388
|
+
stroke: "currentColor",
|
|
389
|
+
strokeWidth: "2",
|
|
390
|
+
strokeLinecap: "round",
|
|
391
|
+
"aria-hidden": "true",
|
|
392
|
+
children: /* @__PURE__ */ jsx("path", { d: "M18 6 6 18M6 6l12 12" })
|
|
393
|
+
}
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
function IconCopy({ className }) {
|
|
397
|
+
return /* @__PURE__ */ jsxs(
|
|
398
|
+
"svg",
|
|
399
|
+
{
|
|
400
|
+
className,
|
|
401
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
402
|
+
viewBox: "0 0 24 24",
|
|
403
|
+
fill: "none",
|
|
404
|
+
stroke: "currentColor",
|
|
405
|
+
strokeWidth: "2",
|
|
406
|
+
strokeLinecap: "round",
|
|
407
|
+
strokeLinejoin: "round",
|
|
408
|
+
"aria-hidden": "true",
|
|
409
|
+
children: [
|
|
410
|
+
/* @__PURE__ */ jsx("rect", { width: "14", height: "14", x: "8", y: "8", rx: "2", ry: "2" }),
|
|
411
|
+
/* @__PURE__ */ jsx("path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" })
|
|
412
|
+
]
|
|
413
|
+
}
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
function IconCheck({ className }) {
|
|
417
|
+
return /* @__PURE__ */ jsx(
|
|
418
|
+
"svg",
|
|
419
|
+
{
|
|
420
|
+
className,
|
|
421
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
422
|
+
viewBox: "0 0 24 24",
|
|
423
|
+
fill: "none",
|
|
424
|
+
stroke: "currentColor",
|
|
425
|
+
strokeWidth: "2",
|
|
426
|
+
strokeLinecap: "round",
|
|
427
|
+
strokeLinejoin: "round",
|
|
428
|
+
"aria-hidden": "true",
|
|
429
|
+
children: /* @__PURE__ */ jsx("path", { d: "M20 6 9 17l-5-5" })
|
|
430
|
+
}
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
var Textarea = React.forwardRef(
|
|
434
|
+
function Textarea2({ className, ...props }, ref) {
|
|
435
|
+
return /* @__PURE__ */ jsx(
|
|
436
|
+
"textarea",
|
|
437
|
+
{
|
|
438
|
+
ref,
|
|
439
|
+
className: cn(
|
|
440
|
+
"flex min-h-20 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
|
441
|
+
className
|
|
442
|
+
),
|
|
443
|
+
...props
|
|
444
|
+
}
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
);
|
|
448
|
+
var Checkbox = React.forwardRef(
|
|
449
|
+
function Checkbox2({ className, label, onCheckedChange, ...props }, ref) {
|
|
450
|
+
const input = /* @__PURE__ */ jsx(
|
|
451
|
+
"input",
|
|
452
|
+
{
|
|
453
|
+
ref,
|
|
454
|
+
type: "checkbox",
|
|
455
|
+
className: cn(
|
|
456
|
+
"size-4 shrink-0 cursor-pointer rounded border-input accent-primary disabled:cursor-not-allowed disabled:opacity-50",
|
|
457
|
+
className
|
|
458
|
+
),
|
|
459
|
+
onChange: (e) => onCheckedChange?.(e.target.checked),
|
|
460
|
+
...props
|
|
461
|
+
}
|
|
462
|
+
);
|
|
463
|
+
if (!label) return input;
|
|
464
|
+
return /* @__PURE__ */ jsxs("label", { className: "inline-flex cursor-pointer items-center gap-2 text-sm", children: [
|
|
465
|
+
input,
|
|
466
|
+
/* @__PURE__ */ jsx("span", { children: label })
|
|
467
|
+
] });
|
|
468
|
+
}
|
|
469
|
+
);
|
|
470
|
+
function SidePanel({
|
|
471
|
+
open,
|
|
472
|
+
onClose,
|
|
473
|
+
title,
|
|
474
|
+
description,
|
|
475
|
+
children,
|
|
476
|
+
className
|
|
477
|
+
}) {
|
|
478
|
+
React.useEffect(() => {
|
|
479
|
+
if (!open) return;
|
|
480
|
+
const onKeyDown = (e) => {
|
|
481
|
+
if (e.key === "Escape") onClose();
|
|
482
|
+
};
|
|
483
|
+
window.addEventListener("keydown", onKeyDown);
|
|
484
|
+
return () => window.removeEventListener("keydown", onKeyDown);
|
|
485
|
+
}, [open, onClose]);
|
|
486
|
+
if (!open) return null;
|
|
487
|
+
return /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50", children: [
|
|
488
|
+
/* @__PURE__ */ jsx(
|
|
489
|
+
"button",
|
|
490
|
+
{
|
|
491
|
+
type: "button",
|
|
492
|
+
"aria-label": "Fermer",
|
|
493
|
+
className: "absolute inset-0 cursor-default bg-foreground/40",
|
|
494
|
+
onClick: onClose
|
|
495
|
+
}
|
|
496
|
+
),
|
|
497
|
+
/* @__PURE__ */ jsxs(
|
|
498
|
+
"aside",
|
|
499
|
+
{
|
|
500
|
+
role: "dialog",
|
|
501
|
+
"aria-modal": true,
|
|
502
|
+
className: cn(
|
|
503
|
+
"absolute inset-y-0 right-0 flex w-full max-w-lg flex-col border-l bg-card shadow-xl",
|
|
504
|
+
className
|
|
505
|
+
),
|
|
506
|
+
children: [
|
|
507
|
+
/* @__PURE__ */ jsxs("header", { className: "flex shrink-0 items-start gap-3 border-b px-5 py-4", children: [
|
|
508
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
509
|
+
title && /* @__PURE__ */ jsx("h2", { className: "text-base font-semibold", children: title }),
|
|
510
|
+
description && /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: description })
|
|
511
|
+
] }),
|
|
512
|
+
/* @__PURE__ */ jsx(
|
|
513
|
+
"button",
|
|
514
|
+
{
|
|
515
|
+
type: "button",
|
|
516
|
+
"aria-label": "Fermer",
|
|
517
|
+
className: "-mr-1 shrink-0 rounded-md p-1 text-muted-foreground hover:bg-accent hover:text-foreground",
|
|
518
|
+
onClick: onClose,
|
|
519
|
+
children: /* @__PURE__ */ jsx(IconX, { className: "size-4" })
|
|
520
|
+
}
|
|
521
|
+
)
|
|
522
|
+
] }),
|
|
523
|
+
/* @__PURE__ */ jsx("div", { className: "flex min-h-0 flex-1 flex-col px-5 py-4", children })
|
|
524
|
+
]
|
|
525
|
+
}
|
|
526
|
+
)
|
|
527
|
+
] });
|
|
528
|
+
}
|
|
529
|
+
var COPY_FEEDBACK_MS = 2e3;
|
|
530
|
+
function CopyButton({
|
|
531
|
+
value,
|
|
532
|
+
label = "Copier",
|
|
533
|
+
copiedLabel = "Copi\xE9",
|
|
534
|
+
variant = "outline",
|
|
535
|
+
size = "sm",
|
|
536
|
+
className
|
|
537
|
+
}) {
|
|
538
|
+
const [state, setState] = React.useState("idle");
|
|
539
|
+
const timeout = React.useRef(void 0);
|
|
540
|
+
React.useEffect(() => () => clearTimeout(timeout.current), []);
|
|
541
|
+
async function copy() {
|
|
542
|
+
const ok = await copyToClipboard(value);
|
|
543
|
+
setState(ok ? "copied" : "error");
|
|
544
|
+
clearTimeout(timeout.current);
|
|
545
|
+
timeout.current = setTimeout(() => setState("idle"), COPY_FEEDBACK_MS);
|
|
546
|
+
}
|
|
547
|
+
return /* @__PURE__ */ jsxs(
|
|
548
|
+
Button,
|
|
549
|
+
{
|
|
550
|
+
type: "button",
|
|
551
|
+
variant,
|
|
552
|
+
size,
|
|
553
|
+
className,
|
|
554
|
+
onClick: () => void copy(),
|
|
555
|
+
children: [
|
|
556
|
+
state === "copied" ? /* @__PURE__ */ jsx(IconCheck, { className: "size-3.5" }) : /* @__PURE__ */ jsx(IconCopy, { className: "size-3.5" }),
|
|
557
|
+
/* @__PURE__ */ jsx("span", { "aria-live": "polite", className: state === "error" ? "text-destructive" : void 0, children: state === "copied" ? copiedLabel : state === "error" ? "Copie impossible" : label })
|
|
558
|
+
]
|
|
559
|
+
}
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
function Popover({
|
|
563
|
+
open,
|
|
564
|
+
onOpenChange,
|
|
565
|
+
trigger,
|
|
566
|
+
children,
|
|
567
|
+
align = "start",
|
|
568
|
+
className
|
|
569
|
+
}) {
|
|
570
|
+
const root = React.useRef(null);
|
|
571
|
+
React.useEffect(() => {
|
|
572
|
+
if (!open) return;
|
|
573
|
+
const onPointerDown = (e) => {
|
|
574
|
+
if (!root.current?.contains(e.target)) onOpenChange(false);
|
|
575
|
+
};
|
|
576
|
+
const onKeyDown = (e) => {
|
|
577
|
+
if (e.key === "Escape") onOpenChange(false);
|
|
578
|
+
};
|
|
579
|
+
document.addEventListener("mousedown", onPointerDown);
|
|
580
|
+
document.addEventListener("keydown", onKeyDown);
|
|
581
|
+
return () => {
|
|
582
|
+
document.removeEventListener("mousedown", onPointerDown);
|
|
583
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
584
|
+
};
|
|
585
|
+
}, [open, onOpenChange]);
|
|
586
|
+
return /* @__PURE__ */ jsxs("div", { ref: root, className: "relative inline-block", children: [
|
|
587
|
+
/* @__PURE__ */ jsx("button", { type: "button", "aria-expanded": open, onClick: () => onOpenChange(!open), children: trigger }),
|
|
588
|
+
open && /* @__PURE__ */ jsx(
|
|
589
|
+
"div",
|
|
590
|
+
{
|
|
591
|
+
className: cn(
|
|
592
|
+
"absolute z-40 mt-1 min-w-48 rounded-md border bg-card p-2 shadow-lg",
|
|
593
|
+
align === "end" ? "right-0" : "left-0",
|
|
594
|
+
className
|
|
595
|
+
),
|
|
596
|
+
children
|
|
597
|
+
}
|
|
598
|
+
)
|
|
599
|
+
] });
|
|
600
|
+
}
|
|
601
|
+
function DropdownMenu({ trigger, items, align = "end", className }) {
|
|
602
|
+
const [open, setOpen] = React.useState(false);
|
|
603
|
+
return /* @__PURE__ */ jsx(
|
|
604
|
+
Popover,
|
|
605
|
+
{
|
|
606
|
+
open,
|
|
607
|
+
onOpenChange: setOpen,
|
|
608
|
+
trigger,
|
|
609
|
+
align,
|
|
610
|
+
className: cn("p-1", className),
|
|
611
|
+
children: /* @__PURE__ */ jsx("div", { role: "menu", className: "flex flex-col", children: items.map((item, i) => /* @__PURE__ */ jsx(
|
|
612
|
+
"button",
|
|
613
|
+
{
|
|
614
|
+
type: "button",
|
|
615
|
+
role: "menuitem",
|
|
616
|
+
disabled: item.disabled,
|
|
617
|
+
className: cn(
|
|
618
|
+
"cursor-pointer rounded px-2 py-1.5 text-left text-sm hover:bg-accent disabled:pointer-events-none disabled:opacity-50",
|
|
619
|
+
item.destructive && "text-destructive"
|
|
620
|
+
),
|
|
621
|
+
onClick: () => {
|
|
622
|
+
setOpen(false);
|
|
623
|
+
item.onSelect();
|
|
624
|
+
},
|
|
625
|
+
children: item.label
|
|
626
|
+
},
|
|
627
|
+
i
|
|
628
|
+
)) })
|
|
629
|
+
}
|
|
630
|
+
);
|
|
631
|
+
}
|
|
339
632
|
|
|
340
|
-
export { AppApiProvider, AppContextProvider, Badge, Button, Card, CardContent, CardHeader, CardTitle, DateRangePicker, Input, Link, Modal, Select, Spinner, Switch, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, cn, useAppApi, useAppContext };
|
|
633
|
+
export { AppApiProvider, AppContextProvider, AppInvokeError, Badge, Button, Card, CardContent, CardHeader, CardTitle, Checkbox, CopyButton, DateRangePicker, DropdownMenu, Input, Link, Modal, Popover, Select, SidePanel, Spinner, Switch, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, Textarea, cn, copyToClipboard, downloadBlob, downloadCsv, toCsv, useAppApi, useAppContext };
|
|
341
634
|
//# sourceMappingURL=index.js.map
|
|
342
635
|
//# sourceMappingURL=index.js.map
|