@abyss-project/commons-front-core 3.0.0 → 3.1.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.
Files changed (125) hide show
  1. package/dist/{CookieConsentSettings.component-CgiQCgFX.js → CookieConsentSettings.component-CSf_M5Gx.js} +505 -443
  2. package/dist/components/Avatars/ProjectAvatar/ProjectAvatar.component.d.ts +3 -0
  3. package/dist/components/Avatars/UserAvatar/UserAvatar.component.d.ts +3 -0
  4. package/dist/components/Cards/ProjectCard/ProjectCard.component.d.ts +1 -0
  5. package/dist/components/CommandPalette/AbyssCommandPalette.d.ts +9 -0
  6. package/dist/components/CommandPalette/types.d.ts +63 -0
  7. package/dist/components/CommandPalette/useCommandPaletteHotkey.d.ts +9 -0
  8. package/dist/components/CompanySearchAutocomplete/CompanySearchAutocomplete.d.ts +25 -0
  9. package/dist/components/CompanySearchAutocomplete/CompanySearchAutocomplete.error.spec.d.ts +1 -0
  10. package/dist/components/CompanySearchAutocomplete/index.d.ts +2 -0
  11. package/dist/components/CookieConsent/AbyssCookieConsentProvider.d.ts +9 -0
  12. package/dist/components/CookieConsent/privacy-link.constant.d.ts +25 -0
  13. package/dist/components/Data/AbyssTable/AbyssTable.component.d.ts +16 -2
  14. package/dist/components/Data/AbyssTable/abyss-table.logic.d.ts +72 -0
  15. package/dist/components/Inputs/AbyssNumberField/AbyssNumberField.component.d.ts +18 -0
  16. package/dist/components/Inputs/AbyssNumberField/format.d.ts +28 -0
  17. package/dist/components/Inputs/CloudImport/CloudImport.component.d.ts +1 -0
  18. package/dist/components/Inputs/ColorPalette/ColorPalette.component.d.ts +2 -0
  19. package/dist/components/Inputs/ContextSelector/ContextSelector.component.d.ts +25 -0
  20. package/dist/components/Inputs/DatePeriodPicker/DatePeriodPicker.component.d.ts +32 -0
  21. package/dist/components/Inputs/DatePeriodPicker/date-period-picker.logic.d.ts +54 -0
  22. package/dist/components/Inputs/DatePeriodPicker/date-period-presets.util.d.ts +8 -0
  23. package/dist/components/Inputs/LanguageFlagLabel/LanguageFlagLabel.component.d.ts +13 -0
  24. package/dist/components/Inputs/LanguageFlagLabel/language-flags.d.ts +10 -0
  25. package/dist/components/Inputs/PasswordInput/PasswordInput.component.d.ts +16 -0
  26. package/dist/components/Inputs/ProjectLinkSelector/index.d.ts +1 -1
  27. package/dist/components/Layouts/AbyssBillingPlan/AbyssBillingPlan.component.d.ts +2 -0
  28. package/dist/components/Layouts/AbyssFamilyBanner/AbyssFamilyBanner.component.d.ts +12 -0
  29. package/dist/components/Layouts/AbyssLayoutProvider/index.d.ts +1 -1
  30. package/dist/components/Layouts/AbyssSidebar/index.d.ts +2 -2
  31. package/dist/components/Modals/AbyssModal/AbyssModal.component.d.ts +6 -0
  32. package/dist/components/Modals/QRCodeModal/QRCodeModal.component.d.ts +1 -0
  33. package/dist/components/Modals/UserSettingsModal/UserSettingsModal.component.d.ts +5 -0
  34. package/dist/components/Modals/UserSettingsModal/index.d.ts +1 -1
  35. package/dist/components/PdfEditor/hooks/use-pdf-document.d.ts +12 -0
  36. package/dist/components/PdfEditor/hooks/use-uploaded-docs.d.ts +9 -0
  37. package/dist/components/PdfEditor/operations/actions.d.ts +14 -0
  38. package/dist/components/PdfEditor/operations/compute-page-order.d.ts +11 -0
  39. package/dist/components/PdfEditor/operations/extract.d.ts +5 -0
  40. package/dist/components/PdfEditor/operations/pdf-data-url.d.ts +4 -0
  41. package/dist/components/PdfEditor/operations/rasterize.d.ts +17 -0
  42. package/dist/components/PdfEditor/operations/redact.d.ts +8 -0
  43. package/dist/components/PdfEditor/operations/rotate-coords.d.ts +25 -0
  44. package/dist/components/PdfEditor/signature/SignaturePicker.d.ts +3 -0
  45. package/dist/components/PdfEditor/signature/SignatureSaveButton.d.ts +3 -0
  46. package/dist/components/PdfEditor/signature/resolve-asset.d.ts +17 -0
  47. package/dist/components/PdfEditor/tools/RedactTool.d.ts +6 -0
  48. package/dist/components/PdfEditor/tools/Toolbar.d.ts +9 -0
  49. package/dist/components/PdfEditor/tools/tool-options-ui.d.ts +1 -0
  50. package/dist/components/PdfEditor/types.d.ts +85 -0
  51. package/dist/components/PdfEditor/viewer/PageOverlay.d.ts +10 -0
  52. package/dist/components/PdfEditor/viewer/PdfCanvas.d.ts +1 -0
  53. package/dist/components/PdfEditor/viewer/ViewerControls.d.ts +4 -0
  54. package/dist/components/QuotaGauge/AbyssQuotaGauge.component.d.ts +39 -0
  55. package/dist/components/QuotaGauge/bubble-placement.d.ts +40 -0
  56. package/dist/components/QuotaGauge/gauge-segments.d.ts +56 -0
  57. package/dist/components/QuotaOffer/AbyssQuotaOffer.component.d.ts +60 -0
  58. package/dist/components/QuotaOffer/quota-offer-state.d.ts +64 -0
  59. package/dist/components/QuotaReached/QuotaReachedDialog.component.d.ts +10 -0
  60. package/dist/components/QuotaReached/billing-quota-error.d.ts +7 -0
  61. package/dist/components/QuotaReached/quota-dialog.store.d.ts +10 -0
  62. package/dist/components/QuotaReached/quota-service-meta.d.ts +4 -0
  63. package/dist/components/Support/SupportCategorySelect.component.d.ts +3 -0
  64. package/dist/components/Support/SupportStatusTimeline.component.d.ts +2 -0
  65. package/dist/components/Support/index.d.ts +2 -2
  66. package/dist/components/Support/support-attachment.logic.d.ts +10 -0
  67. package/dist/components/Support/support-author.logic.d.ts +9 -0
  68. package/dist/components/Support/support-create-stepper.logic.d.ts +13 -0
  69. package/dist/components/Support/support-message-input.logic.d.ts +8 -0
  70. package/dist/components/Support/support-status-timeline.logic.d.ts +16 -0
  71. package/dist/components/Support/types.d.ts +55 -0
  72. package/dist/components/Utils/AbyssBackground/AbyssBackground.component.d.ts +3 -0
  73. package/dist/components/Utils/AbyssFamilyMenuSection/AbyssFamilyMenuSection.d.ts +9 -0
  74. package/dist/components/Utils/AbyssLayoutMenu/index.d.ts +1 -1
  75. package/dist/components/Utils/AbyssLayoutNotifications/AbyssLayoutNotifications.component.d.ts +4 -0
  76. package/dist/components/Utils/AbyssLayoutNotifications/index.d.ts +1 -1
  77. package/dist/components/Utils/ImportButton/ImportButton.component.d.ts +4 -0
  78. package/dist/components/Utils/Toast/AbyssToast.component.d.ts +6 -0
  79. package/dist/components/Utils/WaterParticles/WaterParticles.component.d.ts +8 -0
  80. package/dist/components/index.d.ts +1 -0
  81. package/dist/cookie-consent.js +1 -1
  82. package/dist/hooks/use-public-users.hook.d.ts +39 -0
  83. package/dist/hooks/use-rich-intl.hook.d.ts +27 -5
  84. package/dist/hooks/use-theme-transition.hook.d.ts +13 -0
  85. package/dist/{index-D2ot8DZ4.js → index-6vOdokCY.js} +2 -2
  86. package/dist/{index-cYFrJDdQ.js → index-AUELqW5Y.js} +4 -4
  87. package/dist/{index-DXDfaFmT.js → index-AwC_8lIT.js} +4 -4
  88. package/dist/{index-BN-71mBQ.js → index-B7EugME7.js} +2 -2
  89. package/dist/{index-0WPkkm3C.js → index-BOqcEEHH.js} +3 -3
  90. package/dist/{index-Ctm2mN4b.js → index-BSRrH_Gr.js} +2 -2
  91. package/dist/{index-DpeaEf87.js → index-BToZYvup.js} +2 -2
  92. package/dist/{index-CgqzFzYH.js → index-BlCa_Vlr.js} +2 -2
  93. package/dist/{index-FF8J68dM.js → index-BoYupKIh.js} +23908 -20833
  94. package/dist/{index-2gSMCEyX.js → index-BtiGEZHC.js} +3 -3
  95. package/dist/{index-d9VwJ6nR.js → index-C2a_Y6jF.js} +2 -2
  96. package/dist/{index-C7bGgD6e.js → index-CCphPqf4.js} +2 -2
  97. package/dist/{index-CdpydxgB.js → index-CD9GifGt.js} +2 -2
  98. package/dist/{index-BSa7pbNq.js → index-CDWityvJ.js} +3 -3
  99. package/dist/{index-CAqpnDEx.js → index-CFVsaKv5.js} +4 -4
  100. package/dist/{index-C1NWZqnA.js → index-CtSA_KxY.js} +2 -2
  101. package/dist/{index-C02uLNDS.js → index-D7WU-N9n.js} +2 -2
  102. package/dist/{index-D5IIOkfu.js → index-DUGpZohv.js} +2 -2
  103. package/dist/{index-BqjTmumL.js → index-DrHXb1cg.js} +2 -2
  104. package/dist/{index-B9n8aGer.js → index-GhkzA3Z-.js} +2 -2
  105. package/dist/{index-CXNLGqpN.js → index-RBcLmJfD.js} +1 -1
  106. package/dist/{index-DQgswKg1.js → index-iw6ikhdy.js} +3 -3
  107. package/dist/index.js +185 -184
  108. package/dist/theme/augmentation.d.ts +6 -0
  109. package/dist/theme/index.d.ts +11 -4
  110. package/dist/theme/palettes/common.d.ts +13 -0
  111. package/dist/theme/palettes/dark.d.ts +1 -0
  112. package/dist/theme/palettes/light.d.ts +1 -0
  113. package/dist/theme/tokens/animations.d.ts +3 -0
  114. package/dist/theme/tokens/borders.d.ts +3 -0
  115. package/dist/theme/tokens/colors.d.ts +27 -0
  116. package/dist/theme/tokens/shadows.d.ts +8 -0
  117. package/dist/theme/tokens/transitions.d.ts +4 -0
  118. package/dist/theme/tokens/typography.d.ts +6 -0
  119. package/dist/translations/en.d.ts +3 -0
  120. package/dist/translations/fr.d.ts +3 -0
  121. package/dist/utils/share-notification.utils.d.ts +9 -0
  122. package/dist/utils/space-display-name.utils.d.ts +8 -0
  123. package/package.json +14 -13
  124. package/tsconfig.json +26 -25
  125. package/vite.config.ts +1 -17
@@ -2,7 +2,13 @@ import { ReactNode } from 'react';
2
2
  import { AssetKind } from '@abyss-project/main';
3
3
  import { ISpace, IFile } from '@abyss-project/cloud';
4
4
  import { SupportCloudImportProps } from '../Support/types';
5
+ /**
6
+ * Wiring for cloud-backed insertions inside the editor (currently only the
7
+ * "Insert pages" button). Includes the standard cloud picker props plus a
8
+ * helper to materialize bytes from the picked file.
9
+ */
5
10
  export interface PdfEditorCloudImportProps extends SupportCloudImportProps {
11
+ /** Resolve the raw bytes for a picked cloud file (e.g. via presigned URL). */
6
12
  downloadCloudFile: (cloud: ISpace, file: IFile) => Promise<ArrayBuffer>;
7
13
  }
8
14
  export declare enum PdfOperationType {
@@ -38,8 +44,11 @@ export type PdfOperation = {
38
44
  text: string;
39
45
  fontSize: number;
40
46
  color: string;
47
+ /** Font family; default 'sans' (Helvetica). */
41
48
  fontFamily?: TextFontFamily;
49
+ /** 0..1; default 1. Used by watermarks. */
42
50
  opacity?: number;
51
+ /** Counter-clockwise degrees around the text anchor; default 0 (watermark). */
43
52
  rotationDeg?: number;
44
53
  } | {
45
54
  opId: string;
@@ -68,6 +77,7 @@ export type PdfOperation = {
68
77
  y2: number;
69
78
  color: string;
70
79
  strokeWidth: number;
80
+ /** Fill color for rect/ellipse (hex). null = no fill (outline only). */
71
81
  fill?: string | null;
72
82
  } | {
73
83
  opId: string;
@@ -120,6 +130,21 @@ export type PdfOperation = {
120
130
  opId: string;
121
131
  type: PdfOperationType.FLATTEN;
122
132
  };
133
+ /**
134
+ * One variant of the operation union, addressed by its discriminant.
135
+ *
136
+ * `PdfOperation` is an anonymous union, so a value annotated with it is the WHOLE union: a
137
+ * spread like `{ ...op, x }` then fails, because most members carry no `x`. Callers that
138
+ * genuinely hold one variant (a test builder, a tool that only emits text) say so with this
139
+ * helper instead of widening and casting back.
140
+ */
141
+ export type PdfOperationOf<T extends PdfOperationType> = Extract<PdfOperation, {
142
+ type: T;
143
+ }>;
144
+ /** The subset of operations that carry per-page coordinates. */
145
+ export type PdfPageScopedOperation = Extract<PdfOperation, {
146
+ pageIndex: number;
147
+ }>;
123
148
  export type ExportOption = 'DOWNLOAD' | 'CLOUD_NEW_FILE' | 'CLOUD_REPLACE';
124
149
  export type ExportRequest = {
125
150
  kind: 'DOWNLOAD';
@@ -219,14 +244,21 @@ export interface AssetPaletteItem {
219
244
  id: string;
220
245
  kind: AssetKind;
221
246
  name: string;
247
+ /** Full-resolution download URL for image-mode assets; null for text-mode
248
+ * (TEXT_BLOCK or typed INITIALS). Used when placing the asset on the PDF. */
222
249
  downloadUrl: string | null;
250
+ /** Small (256x256) thumbnail URL — preferred for grid previews so the
251
+ * picker stays light. Falls back to downloadUrl when null. */
223
252
  thumbnailUrl?: string | null;
253
+ /** Raw text payload — set for TEXT_BLOCK and typed-mode INITIALS. */
224
254
  textContent?: string | null;
225
255
  }
226
256
  export interface AssetPaletteOrgBucket {
227
257
  id: string;
228
258
  name: string;
229
259
  items: AssetPaletteItem[];
260
+ /** Optional pre-rendered avatar node (host-injected, e.g. OrganizationAvatar)
261
+ * used by the scope selector inside the picker. */
230
262
  avatar?: ReactNode;
231
263
  }
232
264
  export interface AssetPaletteProjectBucket {
@@ -237,11 +269,23 @@ export interface AssetPaletteProjectBucket {
237
269
  avatar?: ReactNode;
238
270
  }
239
271
  export interface AssetPalette {
272
+ /** Personal assets (the current user's). */
240
273
  personal: AssetPaletteItem[];
274
+ /** Optional pre-rendered avatar for the personal bucket — used by the
275
+ * picker's scope selector to keep visuals consistent with the host app. */
241
276
  personalAvatar?: ReactNode;
277
+ /** Optional org-scoped assets, one bucket per organization the user is a
278
+ * member of. */
242
279
  organizations?: AssetPaletteOrgBucket[];
280
+ /** Optional project-scoped assets, one bucket per project the user has
281
+ * access to. */
243
282
  projects?: AssetPaletteProjectBucket[];
244
283
  }
284
+ /** Called when the user clicks "Save signature" in the picker.
285
+ * The implementation uploads the dataUrl as a SIGNATURE/INITIALS/STAMP asset.
286
+ * - `projectId` set → PROJECT scope (organizationId required too).
287
+ * - `organizationId` set (no projectId) → ORG scope.
288
+ * - neither set → personal (USER) scope. */
245
289
  export type SaveSignatureFn = (params: {
246
290
  dataUrl: string;
247
291
  kind: AssetKind;
@@ -249,29 +293,70 @@ export type SaveSignatureFn = (params: {
249
293
  organizationId?: string | null;
250
294
  projectId?: string | null;
251
295
  }) => Promise<void>;
296
+ /**
297
+ * Optional callback invoked debounced (800ms) after operations change,
298
+ * carrying the FULL list of current operations since the last successful flush.
299
+ * Hosts wiring a custom persistence backend should dedupe by op.opId
300
+ * (idempotent). Leave undefined for pure client-side editing.
301
+ */
252
302
  export type OperationsAdapter = (params: {
253
303
  operations: PdfOperation[];
254
304
  }) => Promise<void>;
305
+ /**
306
+ * Optional host-provided PDF post-processors. pdf-lib cannot write/strip
307
+ * encryption nor truly recompress, so these heavy operations are delegated to
308
+ * the host (which wires a WASM engine like qpdf). Keeping the engine in the host
309
+ * means the multi-MB WASM never bloats the shared bundle: only apps that opt in
310
+ * pay for it, and it loads lazily when the user actually runs the action. The
311
+ * editor surfaces each action only when its callback is provided.
312
+ */
255
313
  export interface PdfSecurity {
314
+ /** Encrypt the baked PDF with a user password; resolves to encrypted bytes. */
256
315
  setPassword?: (bytes: Uint8Array, password: string) => Promise<Uint8Array>;
316
+ /** Decrypt a password-protected PDF; resolves to plain bytes. */
257
317
  removePassword?: (bytes: Uint8Array, password: string) => Promise<Uint8Array>;
318
+ /** Recompress / optimize the baked PDF; resolves to the smaller bytes. */
258
319
  compress?: (bytes: Uint8Array) => Promise<Uint8Array>;
259
320
  }
260
321
  export interface PdfEditorProps {
261
322
  source: PdfSource;
262
323
  mode: PdfEditorMode;
324
+ /** Initial server-loaded operations (replayed on mount). */
263
325
  initialOperations?: PdfOperation[];
326
+ /** Persist new operations server-side (debounced 800ms). */
264
327
  operationsAdapter?: OperationsAdapter;
328
+ /** Authenticated-mode asset palette — enables Saved tab and Save button in SignaturePicker.
329
+ * When omitted, the editor runs in anonymous mode (2-tab picker, no save action). */
265
330
  assetPalette?: AssetPalette;
331
+ /** Resolve a saved (server-stored) asset to a data URL ready to embed.
332
+ * Authenticated hosts implement this via a presigned download so it works
333
+ * cross-origin — a bare fetch of the storage public-access URL is CORS-blocked
334
+ * even though an <img> of the same URL renders. When omitted, the editor falls
335
+ * back to fetching `item.downloadUrl` (correct for anonymous local `data:` URLs). */
266
336
  resolveAssetBytes?: (item: AssetPaletteItem) => Promise<string>;
337
+ /** Surface a user-facing error (e.g. a toast) — used when an asset fails to
338
+ * resolve so the failure is visible instead of silently doing nothing. */
267
339
  onAssetError?: (message: string) => void;
340
+ /** Callback to upload a new signature asset. Wire alongside assetPalette in authenticated mode. */
268
341
  onSaveSignature?: SaveSignatureFn;
269
342
  onExport: (request: ExportRequest) => Promise<void>;
270
343
  exportOptions: ExportOption[];
271
344
  readOnly?: boolean;
345
+ /** Optional: full cloud-import wiring. When provided, the page sidebar's
346
+ * "Insert pages" button uses `ImportButton` (file + cloud picker). */
272
347
  cloudImport?: PdfEditorCloudImportProps;
348
+ /** Optional: deep-link to the host's asset management page (Account →
349
+ * Assets). When set, the signature picker shows a "Manage" button. */
273
350
  manageAssetsUrl?: string;
351
+ /** Optional: when provided, the toolbar shows a "Change file" button that
352
+ * invokes this callback. Hosts typically reset their PdfSource state so
353
+ * the dropzone reappears. */
274
354
  onChangeSource?: () => void;
355
+ /** Optional: host-provided encryption/compression engine (e.g. qpdf-WASM).
356
+ * Each provided callback adds its matching action to the document menu. */
275
357
  pdfSecurity?: PdfSecurity;
358
+ /** Optional: let the user pick an image from AbyssCloud to insert. Resolves to
359
+ * a `data:` URL ready to place, or null if cancelled. When provided, the
360
+ * Insert panel shows an "AbyssCloud" image source. */
276
361
  pickCloudImage?: () => Promise<string | null>;
277
362
  }
@@ -11,17 +11,27 @@ interface PageOverlayProps {
11
11
  pendingSignature: string | null;
12
12
  pendingText: string | null;
13
13
  pendingApplyAllPages: boolean;
14
+ /** Live virtual page indices (pageOrder) — the targets for "apply to all pages". */
14
15
  pageIndices: number[];
15
16
  dispatch: React.Dispatch<EditorAction>;
16
17
  drawColor?: string;
17
18
  drawStrokeWidth?: number;
19
+ /** New-text styling (existing text keeps its own values). */
18
20
  textColor?: string;
19
21
  textSize?: number;
20
22
  textFamily?: TextFontFamily;
23
+ /** Active shape kind (rect/ellipse/line/arrow) when the shape tool is on. */
21
24
  shapeKind?: ShapeKind;
25
+ /** Fill color for rect/ellipse shapes (hex), or null for outline only. */
22
26
  shapeFill?: string | null;
27
+ /** Highlighter color (hex). */
23
28
  highlightColor?: string;
29
+ /** Render scale (px per PDF point) — driven by the zoom control. */
24
30
  scale?: number;
31
+ /** Total displayed rotation (page intrinsic /Rotate + user delta), clockwise.
32
+ * Annotations are stored in unrotated content space; the overlay maps pointer
33
+ * input back to content space and renders inside a rotated inner layer so the
34
+ * preview matches the baked output. */
25
35
  rotation?: number;
26
36
  }
27
37
  export declare const PageOverlay: React.FC<PageOverlayProps>;
@@ -16,6 +16,7 @@ interface PdfCanvasProps {
16
16
  activePageIndex: number;
17
17
  pageOrder: number[];
18
18
  onPageDimensions?: (pageIndex: number, width: number, height: number, totalRotation: number, pointWidth: number) => void;
19
+ /** Reports the display index of the page currently dominant in the viewport. */
19
20
  onVisiblePageChange?: (displayIndex: number) => void;
20
21
  children?: (virtualIndex: number, displayIndex: number) => React.ReactNode;
21
22
  }
@@ -1,5 +1,6 @@
1
1
  import { default as React } from 'react';
2
2
  interface ViewerControlsProps {
3
+ /** Current zoom as a fraction of the base scale (1 = 100%). */
3
4
  zoom: number;
4
5
  onZoomIn: () => void;
5
6
  onZoomOut: () => void;
@@ -10,7 +11,10 @@ interface ViewerControlsProps {
10
11
  currentPage: number;
11
12
  totalPages: number;
12
13
  onGoToPage: (index: number) => void;
14
+ /** Narrow viewport: dock as a flush full-width bottom bar (no floating pills). */
13
15
  compact?: boolean;
16
+ /** Width (px) of the inline page sidebar, so the floating pills center over the
17
+ * PAGE region rather than the whole area (kept in sync with the placement banner). */
14
18
  sidebarWidth?: number;
15
19
  }
16
20
  export declare const ViewerControls: React.FC<ViewerControlsProps>;
@@ -1,14 +1,53 @@
1
1
  import { default as React } from 'react';
2
2
  import { QuotaGaugeInput } from './gauge-segments';
3
+ /** Un repère sur l'axe : sa valeur, ce qu'il implique, et le raccourci qui permet d'agir. */
3
4
  export interface QuotaMarker {
4
5
  kind: 'threshold' | 'included' | 'ceiling';
5
6
  value: number;
7
+ /** Libellé de l'action, et ce qu'elle déclenche. Sans elle, la bulle informe seulement. */
6
8
  onAction?: () => void;
7
9
  }
8
10
  export interface AbyssQuotaGaugeProps extends QuotaGaugeInput {
9
11
  markers?: QuotaMarker[];
12
+ /** Rend une valeur dans l'unité de la métrique — « 1,5 To », « 12 000 événements ». */
10
13
  formatValue?: (value: number) => string;
14
+ /** Hauteur de la barre. La bulle et les repères s'y adaptent. */
11
15
  height?: number;
16
+ /**
17
+ * Tait la mention « le dépassement n'est pas autorisé » sous l'axe.
18
+ *
19
+ * À poser quand le TEXTE PORTEUR le dit déjà. Dans le bandeau compact du satellite, la phrase
20
+ * apparaissait deux fois à trois lignes d'intervalle : une fois sous la jauge, une fois dans le
21
+ * message d'état. Répétée, elle n'informe plus, elle encombre — et c'est exactement le genre de
22
+ * détail qui fait qu'un écran « n'est pas beau » sans qu'on sache dire pourquoi.
23
+ */
12
24
  hideOverageNote?: boolean;
13
25
  }
26
+ /**
27
+ * LA JAUGE DE QUOTA PARTAGÉE.
28
+ *
29
+ * ═══ POURQUOI ELLE EXISTE ═══
30
+ *
31
+ * Aucune barre de progression partagée n'existait : chaque surface de facturation réimplémentait la
32
+ * sienne, avec ses propres couleurs de seuil. Vérifié — `commons-front-core` ne portait qu'une
33
+ * boîte de dialogue « quota atteint », et quatre écrans du hub avaient chacun leur `LinearProgress`.
34
+ *
35
+ * ═══ IL N'Y A PAS DE LÉGENDE, ET C'EST DÉLIBÉRÉ ═══
36
+ *
37
+ * Chaque SEGMENT porte à la fois sa couleur ET son explication. La solution « remplissage décoratif
38
+ * plus zone de survol invisible » ferait deux sources à garder synchronisées ; un seul nœud ne peut
39
+ * pas dériver. Le survol l'éclaire et fait reculer les autres — le contraste fait la lecture.
40
+ *
41
+ * ═══ UNE BULLE QUI CONTIENT UN BOUTON N'EST PAS UNE BULLE ═══
42
+ *
43
+ * Un pseudo-élément CSS ne se clique pas. Il faut un vrai nœud, survolable, qui reste ouvert quand
44
+ * la souris y entre — et PORTALISÉ, sinon l'`overflow: hidden` de la carte porteuse le rogne.
45
+ *
46
+ * ═══ LE CALCUL N'EST PAS ICI ═══
47
+ *
48
+ * Le placement des bulles et le découpage de l'axe vivent dans deux modules purs, testés sans
49
+ * navigateur sur les quatre largeurs de la spec. Les laisser dans le composant les aurait rendus
50
+ * vérifiables seulement à l'œil — et le premier passage du contrôle signalait quinze défauts sur
51
+ * quinze en mobile, dont aucun n'était visible à la relecture.
52
+ */
14
53
  export declare const AbyssQuotaGauge: React.FC<AbyssQuotaGaugeProps>;
@@ -1,7 +1,47 @@
1
+ /**
2
+ * LE PLACEMENT D'UNE BULLE DE REPÈRE SUR LA JAUGE DE QUOTA.
3
+ *
4
+ * ═══ POURQUOI CE CALCUL EST UNE FONCTION PURE, ET PAS DU CSS ═══
5
+ *
6
+ * Trois pièges, tous rencontrés en MESURANT plutôt qu'en regardant. Le contrôle qui a servi aux
7
+ * maquettes signalait quinze défauts sur quinze en mobile, et aucune relecture ne les aurait vus.
8
+ *
9
+ * 1. UN SEUIL EN POURCENTAGE NE MARCHE PAS. Le point de bascule entre « bulle centrée » et
10
+ * « bulle collée au bord » dépend de la largeur RENDUE, qui change avec l'écran ET avec la
11
+ * langue — un libellé allemand décale le basculement d'un repère qu'un libellé français
12
+ * laissait tenir.
13
+ *
14
+ * 2. `left: 0` SE RÉFÈRE AU PORTEUR, PAS À LA JAUGE. Ancrer sur un bord ne suffit pas : il faut
15
+ * convertir en coordonnées de jauge, sinon la bulle d'un repère à 80 % part 80 % trop à
16
+ * droite.
17
+ *
18
+ * 3. SUR ÉCRAN ÉTROIT LA BULLE EST PLUS LARGE QUE LA JAUGE. Aucune position ne la fait alors
19
+ * tenir : il faut la RÉTRÉCIR d'abord. C'est la ligne `min(...)` ci-dessous, et c'est elle qui
20
+ * règle les quinze défauts mobiles.
21
+ *
22
+ * Sortir ce calcul du composant le rend testable sans navigateur, aux quatre largeurs de la spec,
23
+ * sans monter de DOM ni simuler un survol.
24
+ */
25
+ /** La largeur de confort d'une bulle, en pixels. Au-delà, le texte se met à flotter. */
1
26
  export declare const BUBBLE_MAX_WIDTH = 232;
2
27
  export interface BubblePlacement {
28
+ /** Largeur retenue : la largeur de confort, ou celle de la jauge si elle est plus étroite. */
3
29
  width: number;
30
+ /** Décalage du bord GAUCHE de la bulle, en coordonnées de jauge. */
4
31
  left: number;
32
+ /**
33
+ * Décalage de la flèche À L'INTÉRIEUR de la bulle.
34
+ *
35
+ * Elle reste sous le porteur où que la bulle atterrisse : une flèche figée au centre pointerait
36
+ * dans le vide dès que la bulle est bornée par un bord.
37
+ */
5
38
  arrow: number;
6
39
  }
40
+ /**
41
+ * Place une bulle sous un repère.
42
+ *
43
+ * @param markerCenter position du repère, en pixels depuis le bord gauche de la JAUGE
44
+ * @param gaugeWidth largeur rendue de la jauge, en pixels
45
+ * @param maxWidth largeur de confort de la bulle
46
+ */
7
47
  export declare const placeBubble: (markerCenter: number, gaugeWidth: number, maxWidth?: number) => BubblePlacement;
@@ -1,24 +1,80 @@
1
+ /**
2
+ * LES SEGMENTS D'UNE JAUGE DE QUOTA, ET L'AXE SUR LEQUEL ILS SE POSENT.
3
+ *
4
+ * ═══ L'AXE S'ARRÊTE AU PLAFOND, PAS AU MAXIMUM VENDABLE ═══
5
+ *
6
+ * Porter l'axe jusqu'au maximum libre-service écraserait la zone utile : un compte à 200 Go inclus
7
+ * dont le maximum est 5 To verrait sa limite incluse et son seuil d'alerte confondus sur 4 % de la
8
+ * barre. L'axe s'arrête donc au PLAFOND — le maximum réel du compte —, et le fait qu'il soit
9
+ * relevable devient une affordance plutôt qu'une zone à l'échelle qui mentirait.
10
+ *
11
+ * Sans plafond fixé, l'axe s'arrête à la limite incluse.
12
+ *
13
+ * ═══ POURQUOI CE CALCUL EST PUR ═══
14
+ *
15
+ * Un segment porte à la fois sa couleur ET son explication. La solution « remplissage décoratif
16
+ * plus zone de survol invisible » ferait deux sources à garder synchronisées ; un seul nœud ne peut
17
+ * pas dériver. Encore faut-il que ce nœud sache exactement où il commence et où il finit — d'où ce
18
+ * calcul, testable sans monter de DOM.
19
+ */
20
+ /** Ce qu'un segment raconte. Le nom porte le sens, pas la couleur : le thème décide du rendu. */
1
21
  export declare enum QuotaSegmentKind {
22
+ /** Consommé à l'intérieur de ce que le plan inclut. */
2
23
  USED_INCLUDED = "USED_INCLUDED",
24
+ /** Consommé au-delà de l'inclus — facturé en dépassement. */
3
25
  USED_OVERAGE = "USED_OVERAGE",
26
+ /** Encore inclus, pas encore consommé. */
4
27
  FREE_INCLUDED = "FREE_INCLUDED",
28
+ /** Dépassement encore autorisé avant le plafond. */
5
29
  FREE_OVERAGE = "FREE_OVERAGE"
6
30
  }
7
31
  export interface QuotaSegment {
8
32
  kind: QuotaSegmentKind;
33
+ /** Début et fin en unités de la métrique. */
9
34
  from: number;
10
35
  to: number;
36
+ /** Part de l'axe, entre 0 et 1 — ce que le rendu utilise pour sa largeur. */
11
37
  ratio: number;
12
38
  }
13
39
  export interface QuotaAxis {
40
+ /** Fin de l'axe, en unités de la métrique. */
14
41
  max: number;
15
42
  segments: QuotaSegment[];
43
+ /** `true` quand l'axe s'arrête à l'inclus faute de plafond : le dépassement n'est pas autorisé. */
16
44
  overageDisallowed: boolean;
17
45
  }
18
46
  export interface QuotaGaugeInput {
47
+ /** Consommation courante. */
19
48
  current: number;
49
+ /** Ce que le plan et les modules incluent. `-1` vaut illimité. */
20
50
  includedLimit: number;
51
+ /**
52
+ * Le plafond du compte, au-delà duquel tout est refusé. `null` : aucun plafond posé, donc aucun
53
+ * dépassement autorisé. `-1` : dépassement sans borne.
54
+ */
21
55
  ceiling: number | null;
22
56
  }
57
+ /**
58
+ * Découpe l'axe en segments contigus.
59
+ *
60
+ * ═══ LES TROIS CAS QUI NE SE DEVINENT PAS ═══
61
+ *
62
+ * ILLIMITÉ — une limite à `-1` n'a pas d'axe : une barre de progression vers l'infini est toujours
63
+ * à zéro pour cent, ce qui est vrai et inutile. On rend un axe vide, et le rendu affiche un état
64
+ * plutôt qu'une jauge.
65
+ *
66
+ * DÉPASSEMENT SANS BORNE — le plafond `-1` ne peut pas terminer l'axe. On le termine à la
67
+ * consommation courante quand elle dépasse l'inclus, sinon à l'inclus : l'axe suit alors le client
68
+ * au lieu de lui montrer un infini vide.
69
+ *
70
+ * CONSOMMATION AU-DELÀ DU PLAFOND — elle arrive : un plafond baissé après coup, ou un compteur
71
+ * rattrapé en retard. L'axe s'étend jusqu'à elle, sinon le segment consommé sortirait de la barre.
72
+ */
23
73
  export declare const buildQuotaAxis: ({ current, includedLimit, ceiling, }: QuotaGaugeInput) => QuotaAxis;
74
+ /**
75
+ * La position d'un repère sur l'axe, en part de l'axe entre 0 et 1.
76
+ *
77
+ * Rend `null` quand le repère n'a pas sa place — pas d'axe, ou une valeur au-delà de son bout. Le
78
+ * rendre à 1 le collerait au bord droit et le ferait passer pour un plafond, ce qu'il n'est pas.
79
+ */
24
80
  export declare const markerRatio: (value: number | null, axis: QuotaAxis) => number | null;
@@ -1,23 +1,83 @@
1
1
  import { default as React } from 'react';
2
2
  import { QuotaMarker } from '../QuotaGauge';
3
3
  import { QuotaOfferInput } from './quota-offer-state';
4
+ /** Une branche chiffrée. Le composant ne calcule RIEN : il reçoit le montant déjà fait. */
4
5
  export interface QuotaOfferBranch {
5
6
  kind: 'extension' | 'pack' | 'overage' | 'ceiling' | 'unlock';
7
+ /**
8
+ * Montant formaté, prêt à afficher — « 5,35 € ».
9
+ *
10
+ * `null` veut dire INDISPONIBLE, pas gratuit : le composant l'affiche comme tel plutôt que de
11
+ * l'estimer. Un prix inventé côté client diverge de la facture au premier changement de barème.
12
+ */
6
13
  price: string | null;
14
+ /** Taille du lot, pour l'extension. */
7
15
  lot?: number;
8
16
  onSelect?: () => void;
9
17
  }
10
18
  export interface AbyssQuotaOfferProps extends QuotaOfferInput {
19
+ /** Unité affichée — « Go », « membres ». */
11
20
  unit: string;
21
+ /** La métrique, telle que le contrat d'URL l'attend. */
12
22
  metricKey: string;
13
23
  branches: QuotaOfferBranch[];
14
24
  variant?: 'compact' | 'expanded';
15
25
  markers?: QuotaMarker[];
26
+ /**
27
+ * Rend une valeur dans l'unité de la métrique. Par défaut, `Intl` — jamais `String(v)`, qui
28
+ * affichait « 110000 » là où le client lit « 110 000 ».
29
+ */
16
30
  formatValue?: (value: number) => string;
31
+ /**
32
+ * La RACINE de facturation du hub — pas l'URL complète.
33
+ *
34
+ * LE SATELLITE NE LA CONNAÎT PAS : il la reçoit, comme il reçoit les prix. Embarquer un tunnel
35
+ * d'achat dans un satellite y dupliquerait Stripe, le moyen de paiement et les items
36
+ * d'abonnement — tout ce qui vit dans le hub, neuf fois.
37
+ *
38
+ * Le lien profond est construit ICI, avec la métrique, le geste visé et l'adresse de retour :
39
+ * neuf satellites qui le fabriqueraient chacun de leur côté finiraient par en perdre un morceau.
40
+ */
41
+ /**
42
+ * Le service qui sert cette métrique. Le point d'entrée d'offre en exige le couple complet ; le
43
+ * composant le reçoit du serveur, avec l'offre qu'il affiche.
44
+ */
17
45
  serviceKey: string;
18
46
  billingBaseUrl?: string;
47
+ /**
48
+ * L'organisation dont le quota est affiché, quand il y en a une.
49
+ *
50
+ * Le hub sert la consommation d'une organisation sur une route DISTINCTE, qui porte
51
+ * l'identifiant dans le chemin. Sans lui, un membre d'une organisation atterrit sur la
52
+ * consommation de son compte PERSONNEL : des chiffres réels, mais ceux d'un autre compte que
53
+ * celui qu'il regardait — pire qu'une page d'erreur, qui au moins se voit.
54
+ */
19
55
  organizationId?: string;
56
+ /** Où revenir après le geste. Généralement l'adresse courante du satellite. */
20
57
  returnUrl?: string;
21
58
  onNavigateToBilling?: () => void;
22
59
  }
60
+ /**
61
+ * L'OFFRE DE QUOTA, ET SES HUIT ÉTATS.
62
+ *
63
+ * ═══ CE QU'IL NE FAIT PAS ═══
64
+ *
65
+ * Il ne CALCULE aucun prix et ne DÉRIVE aucun état : les deux arrivent tout faits, l'un du serveur,
66
+ * l'autre d'une fonction pure testée à part. Un `if` enfoui dans le rendu ne se vérifie qu'à l'œil,
67
+ * sur les quelques cas qu'on pense à ouvrir — et se tromper d'état a deux coûts opposés, tous deux
68
+ * chers.
69
+ *
70
+ * ═══ LES DEUX VARIANTS, ET CE QUI LES DISTINGUE VRAIMENT ═══
71
+ *
72
+ * COMPACT vit dans un satellite : une ligne au-dessus du contenu, JAMAIS devant. Son action REDIRIGE
73
+ * vers le hub — le satellite informe et route, il ne vend pas.
74
+ *
75
+ * ÉTENDU vit dans les pages de facturation du hub : la jauge et les branches chiffrées.
76
+ *
77
+ * ═══ AMBRE SE FERME, ROUGE NE SE FERME PAS ═══
78
+ *
79
+ * En approche, le bandeau est informatif et FERMABLE : l'utilisateur travaille, on ne lui bloque pas
80
+ * l'écran. Au refus, l'action vient d'échouer, le bandeau porte la cause et n'est plus fermable —
81
+ * c'est le seul moment où il a le droit d'insister.
82
+ */
23
83
  export declare const AbyssQuotaOffer: React.FC<AbyssQuotaOfferProps>;
@@ -1,22 +1,86 @@
1
+ /**
2
+ * LES HUIT ÉTATS D'UNE OFFRE DE QUOTA, DÉRIVÉS PLUTÔT QUE DÉCIDÉS À L'AFFICHAGE.
3
+ *
4
+ * ═══ POURQUOI CETTE DÉRIVATION EST PURE ═══
5
+ *
6
+ * Se tromper d'état a deux coûts opposés, et tous deux sont chers : proposer un achat à un client
7
+ * SAIN décrédibilise l'alerte quand elle arrive vraiment, et ne rien proposer à un client BLOQUÉ le
8
+ * laisse devant un refus sans issue. Un `if` enfoui dans le rendu ne se vérifie qu'à l'œil, sur les
9
+ * quelques cas qu'on pense à ouvrir.
10
+ *
11
+ * ═══ CE QUE LE COMPOSANT NE FAIT PAS ═══
12
+ *
13
+ * Il ne CALCULE aucun prix. Les montants arrivent déjà chiffrés par le serveur, par la même
14
+ * fonction qui sert l'achat, la réactivation d'un module suspendu et l'aperçu de descente de gamme.
15
+ * Un barème dupliqué côté client dérive au premier changement, et l'écart se voit sur la facture.
16
+ */
17
+ /** Les huit états, dans l'ordre de la spec. */
1
18
  export declare enum QuotaOfferState {
19
+ /** 1 — sous le seuil. AUCUNE action proposée. */
2
20
  HEALTHY = "HEALTHY",
21
+ /** 2 — seuil d'alerte franchi. Mention discrète. */
3
22
  APPROACHING = "APPROACHING",
23
+ /** 3 — limite atteinte, dépassement possible. Les trois branches. */
4
24
  LIMIT_REACHED = "LIMIT_REACHED",
25
+ /** 4 — limite atteinte, dépassement IMPOSSIBLE. Deux branches, la troisième expliquée. */
5
26
  LIMIT_REACHED_NO_OVERAGE = "LIMIT_REACHED_NO_OVERAGE",
27
+ /** 5 — en dépassement. L'économie chiffrée. */
6
28
  IN_OVERAGE = "IN_OVERAGE",
29
+ /** 6 — plafond atteint. Refus dur, deux issues. */
7
30
  CEILING_REACHED = "CEILING_REACHED",
31
+ /** 7 — réglage : pas de jauge, une option. */
8
32
  SETTING = "SETTING",
33
+ /** 8 — extension suspendue parce que le plan la couvre. */
9
34
  SUSPENDED_BY_PLAN = "SUSPENDED_BY_PLAN"
10
35
  }
11
36
  export interface QuotaOfferInput {
12
37
  current: number;
38
+ /** Ce que le plan et les modules incluent. `-1` vaut illimité. */
13
39
  includedLimit: number;
40
+ /** Plafond du compte. `null` : aucun, donc aucun dépassement autorisé. `-1` : sans borne. */
14
41
  ceiling: number | null;
42
+ /** Seuil d'alerte, en part de l'inclus — 0,8 pour 80 %. */
15
43
  alertThreshold: number;
44
+ /** Une métrique de RÉGLAGE ne se consomme pas : elle se débloque. */
16
45
  isSetting: boolean;
46
+ /** Le module qui étend cette métrique est suspendu parce que le plan le couvre désormais. */
17
47
  moduleSuspendedByPlan: boolean;
48
+ /** Taille d'un lot, dans l'unité de la métrique. `null` : aucun module ne vend cette métrique. */
18
49
  lotSize: number | null;
19
50
  }
51
+ /**
52
+ * Décide l'état à afficher.
53
+ *
54
+ * ═══ L'ORDRE DES TESTS EST LA RÈGLE ═══
55
+ *
56
+ * Il ne s'agit pas de style : un client peut être À LA FOIS en dépassement et avec un module
57
+ * suspendu, ou au plafond d'une métrique de réglage. L'ordre dit lequel de ces faits le concerne le
58
+ * plus, et c'est toujours le plus contraignant — celui qui l'empêche d'agir passe avant celui qui
59
+ * lui coûte de l'argent, qui passe avant celui qui l'informe.
60
+ */
20
61
  export declare const deriveQuotaOfferState: ({ current, includedLimit, ceiling, alertThreshold, isSetting, moduleSuspendedByPlan, }: QuotaOfferInput) => QuotaOfferState;
62
+ /**
63
+ * `true` quand l'état autorise une proposition.
64
+ *
65
+ * L'état SAIN et l'état APPROCHE en sont exclus : le premier ne dit rien, le second mentionne sans
66
+ * vendre. Et la SUSPENSION explique, elle ne propose pas — l'option est déjà couverte par le plan.
67
+ */
21
68
  export declare const offersAction: (state: QuotaOfferState) => boolean;
69
+ /**
70
+ * `true` quand il devient MOINS CHER d'acheter un lot que de subir le dépassement.
71
+ *
72
+ * ═══ L'ARITHMÉTIQUE, PARCE QU'ELLE DÉCIDE DU SEUIL ═══
73
+ *
74
+ * Le dépassement coûte le DOUBLE de la tranche d'entrée (`OVERAGE_MULTIPLIER`), et un lot de `N`
75
+ * unités coûte à peu près `N × tranche`. Dépasser de `X` unités coûte donc `2 × X × tranche`, et
76
+ * acheter devient plus avantageux dès que :
77
+ *
78
+ * 2 × X × tranche > N × tranche ⟺ X > N / 2
79
+ *
80
+ * D'où « la moitié d'un lot », qui n'est pas un chiffre rond choisi au hasard mais le point exact
81
+ * où les deux courbes se croisent.
82
+ *
83
+ * Rend `false` quand aucun module ne vend la métrique : il n'y a pas de lot à proposer, et
84
+ * suggérer un achat impossible est pire que de se taire.
85
+ */
22
86
  export declare const shouldSuggestModule: (current: number, includedLimit: number, lotSize: number | null) => boolean;
@@ -1,5 +1,15 @@
1
1
  import { default as React } from 'react';
2
2
  export interface QuotaReachedDialogProps {
3
+ /**
4
+ * Called when the user clicks the upgrade CTA. The host decides where to send
5
+ * them (internal route or the main dashboard billing URL). When omitted, the
6
+ * CTA button is hidden and only an explanatory message + close button show.
7
+ */
3
8
  onUpgrade?: () => void;
4
9
  }
10
+ /**
11
+ * Shared "quota reached" dialog. Triggered imperatively by the billing-quota
12
+ * axios interceptor (see attachQuotaInterceptor) when a satellite blocks a
13
+ * create because the current plan's limit is hit.
14
+ */
5
15
  export declare const QuotaReachedDialog: React.FC<QuotaReachedDialogProps>;
@@ -1,4 +1,11 @@
1
1
  import { AxiosInstance } from 'axios';
2
2
  import { QuotaDialogPayload } from './quota-dialog.store';
3
+ /**
4
+ * Detects a billing-quota block in an axios error and resolves it into the
5
+ * payload the quota dialog needs. Returns null for anything that is not a
6
+ * recognizable quota block. The local `parseQuotaMetric` helper enforces the
7
+ * backend contract (access_not_allowed code + the exact quota message shape) and
8
+ * returns the offending metric.
9
+ */
3
10
  export declare const parseQuotaError: (error: unknown) => QuotaDialogPayload | null;
4
11
  export declare const attachQuotaInterceptor: (instances: AxiosInstance[]) => void;
@@ -1,8 +1,11 @@
1
1
  import { BillingMetric, BillingService } from '@abyss-project/main';
2
2
  import { UseBoundStore, StoreApi } from 'zustand';
3
3
  export interface QuotaDialogPayload {
4
+ /** Metric that hit its ceiling (e.g. TOOLS_BOARD_COUNT). */
4
5
  metric: BillingMetric;
6
+ /** Owning service, resolved from the metric. */
5
7
  service: BillingService;
8
+ /** Raw backend message, kept for debugging / fallback display. */
6
9
  rawMessage?: string;
7
10
  }
8
11
  interface QuotaDialogState {
@@ -11,5 +14,12 @@ interface QuotaDialogState {
11
14
  show: (payload: QuotaDialogPayload) => void;
12
15
  hide: () => void;
13
16
  }
17
+ /**
18
+ * Shared store driving the global "quota reached" dialog across every Abyss
19
+ * front. The billing-quota axios interceptor lives outside the React tree, so
20
+ * it pushes into this store imperatively via
21
+ * `useQuotaDialogStore.getState().show(...)`. A `<QuotaReachedDialog />`
22
+ * mounted at the host's app root subscribes and renders.
23
+ */
14
24
  export declare const useQuotaDialogStore: UseBoundStore<StoreApi<QuotaDialogState>>;
15
25
  export {};
@@ -6,6 +6,10 @@ interface ServiceMeta {
6
6
  icon: React.ReactElement;
7
7
  color: PaletteKey;
8
8
  }
9
+ /**
10
+ * How each billing service is displayed in the shared quota dialog. Mirrors
11
+ * the front-v2 SERVICE_META so the look is consistent across hosts.
12
+ */
9
13
  export declare const QUOTA_SERVICE_META: Record<BillingService, ServiceMeta>;
10
14
  export declare const getQuotaServiceColor: (theme: Theme, service: BillingService) => string;
11
15
  export declare const renderQuotaServiceIcon: (service: BillingService, fontSize: number) => React.ReactElement;
@@ -1,11 +1,14 @@
1
1
  import { default as React } from 'react';
2
2
  import { SupportTicketCategory } from '@abyss-project/main';
3
3
  interface SupportCategorySelectProps {
4
+ /** Single value mode (create form) */
4
5
  value?: SupportTicketCategory | null;
5
6
  onChange?: (value: SupportTicketCategory | null) => void;
7
+ /** Multiple value mode (filter) */
6
8
  multiple?: boolean;
7
9
  multipleValue?: SupportTicketCategory[];
8
10
  onMultipleChange?: (value: SupportTicketCategory[]) => void;
11
+ /** Common props */
9
12
  label?: string;
10
13
  placeholder?: string;
11
14
  size?: 'small' | 'medium';