@donotdev/ui 0.0.11 → 0.0.12

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.
@@ -18,6 +18,8 @@ export interface GameContainerProps {
18
18
  justify?: 'center' | 'start' | 'end' | 'between';
19
19
  /** Disable ScrollArea (use plain overflow) - for interactive content */
20
20
  disableScrollArea?: boolean;
21
+ /** Content width: 'full' (default) or 'narrow' (constrained by --narrow-content-max) */
22
+ contentVariant?: 'full' | 'narrow';
21
23
  /** Additional className for content wrapper */
22
24
  contentClassName?: string;
23
25
  /** Additional className for CTA button */
@@ -70,5 +72,5 @@ export interface GameContainerProps {
70
72
  * @since 0.0.1
71
73
  * @author AMBROISE PARK Consulting
72
74
  */
73
- export declare function GameContainer({ content, cta, align, justify, disableScrollArea, contentClassName, ctaClassName, }: GameContainerProps): import("react/jsx-runtime").JSX.Element;
75
+ export declare function GameContainer({ content, cta, align, justify, disableScrollArea, contentVariant, contentClassName, ctaClassName, }: GameContainerProps): import("react/jsx-runtime").JSX.Element;
74
76
  //# sourceMappingURL=GameContainer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"GameContainer.d.ts","sourceRoot":"","sources":["../../../src/components/layout/GameContainer.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,0DAA0D;IAC1D,OAAO,EAAE,SAAS,CAAC;IAEnB,0CAA0C;IAC1C,GAAG,CAAC,EAAE;QACJ,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,IAAI,CAAC;QACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;KACpD,CAAC;IAEF,yCAAyC;IACzC,KAAK,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;IAEvC,uCAAuC;IACvC,OAAO,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,KAAK,GAAG,SAAS,CAAC;IAEjD,wEAAwE;IACxE,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAE5B,+CAA+C;IAC/C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B,0CAA0C;IAC1C,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EACP,GAAG,EACH,KAAgB,EAChB,OAAkB,EAClB,iBAAyB,EACzB,gBAAgB,EAChB,YAAY,GACb,EAAE,kBAAkB,2CAmCpB"}
1
+ {"version":3,"file":"GameContainer.d.ts","sourceRoot":"","sources":["../../../src/components/layout/GameContainer.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,0DAA0D;IAC1D,OAAO,EAAE,SAAS,CAAC;IAEnB,0CAA0C;IAC1C,GAAG,CAAC,EAAE;QACJ,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,IAAI,CAAC;QACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;KACpD,CAAC;IAEF,yCAAyC;IACzC,KAAK,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;IAEvC,uCAAuC;IACvC,OAAO,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,KAAK,GAAG,SAAS,CAAC;IAEjD,wEAAwE;IACxE,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAE5B,wFAAwF;IACxF,cAAc,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IAEnC,+CAA+C;IAC/C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B,0CAA0C;IAC1C,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EACP,GAAG,EACH,KAAgB,EAChB,OAAkB,EAClB,iBAAyB,EACzB,cAAuB,EACvB,gBAAgB,EAChB,YAAY,GACb,EAAE,kBAAkB,2CA0CpB"}
@@ -57,6 +57,7 @@ import { cn } from '@donotdev/components';
57
57
  * @since 0.0.1
58
58
  * @author AMBROISE PARK Consulting
59
59
  */
60
- export function GameContainer({ content, cta, align = 'center', justify = 'center', disableScrollArea = false, contentClassName, ctaClassName, }) {
61
- return (_jsxs("div", { className: "dndev-game-container", children: [_jsx("div", { className: cn('dndev-game-container__content', contentClassName), "data-align": align, "data-justify": justify, children: disableScrollArea ? (_jsx("div", { style: { overflow: 'auto', overscrollBehavior: 'contain' }, children: content })) : (_jsx(ScrollArea, { className: "dndev-game-container__scroll", children: content })) }), cta && (_jsx("div", { className: cn('dndev-game-container__cta', ctaClassName), children: _jsx(Button, { onClick: cta.onClick, disabled: cta.disabled ?? false, variant: cta.variant ?? 'default', className: "dndev-game-container__cta-button", children: cta.label }) }))] }));
60
+ export function GameContainer({ content, cta, align = 'center', justify = 'center', disableScrollArea = false, contentVariant = 'full', contentClassName, ctaClassName, }) {
61
+ const contentInner = disableScrollArea ? (_jsx("div", { style: { overflow: 'auto', overscrollBehavior: 'contain' }, children: content })) : (_jsx(ScrollArea, { className: "dndev-game-container__scroll", children: content }));
62
+ return (_jsxs("div", { className: "dndev-game-container", children: [_jsx("div", { className: cn('dndev-game-container__content', contentClassName), "data-align": align, "data-justify": justify, "data-content-variant": contentVariant, children: contentVariant === 'narrow' ? (_jsx("div", { className: "dndev-game-container__content-narrow", children: contentInner })) : (contentInner) }), cta && (_jsx("div", { className: cn('dndev-game-container__cta', ctaClassName), children: _jsx(Button, { onClick: cta.onClick, disabled: cta.disabled ?? false, variant: cta.variant ?? 'default', className: "dndev-game-container__cta-button", children: cta.label }) }))] }));
62
63
  }
@@ -3,7 +3,7 @@ import type { ComponentType, ReactNode } from 'react';
3
3
  /**
4
4
  * Page container variants - semantic layout patterns
5
5
  */
6
- export type PageContainerVariant = 'full' | 'standard' | 'docs' | 'fixed';
6
+ export type PageContainerVariant = 'full' | 'standard' | 'docs' | 'narrow' | 'fixed';
7
7
  /**
8
8
  * PageContainer component props
9
9
  */
@@ -1 +1 @@
1
- {"version":3,"file":"PageContainer.d.ts","sourceRoot":"","sources":["../../../src/components/layout/PageContainer.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAK9C,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEtD;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAC5B,MAAM,GACN,UAAU,GACV,MAAM,GACN,OAAO,CAAC;AAEZ;;GAEG;AACH,UAAU,kBAAkB;IAC1B,4BAA4B;IAC5B,OAAO,CAAC,EAAE,oBAAoB,CAAC;IAE/B,uDAAuD;IACvD,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB,6BAA6B;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,oBAAoB;IACpB,QAAQ,EAAE,SAAS,CAAC;IAEpB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,wCAAwC;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,aAAa,EAAE,aAAa,CAAC,kBAAkB,CA4B3D,CAAC"}
1
+ {"version":3,"file":"PageContainer.d.ts","sourceRoot":"","sources":["../../../src/components/layout/PageContainer.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAK9C,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEtD;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAC5B,MAAM,GACN,UAAU,GACV,MAAM,GACN,QAAQ,GACR,OAAO,CAAC;AAEZ;;GAEG;AACH,UAAU,kBAAkB;IAC1B,4BAA4B;IAC5B,OAAO,CAAC,EAAE,oBAAoB,CAAC;IAE/B,uDAAuD;IACvD,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB,6BAA6B;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,oBAAoB;IACpB,QAAQ,EAAE,SAAS,CAAC;IAEpB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,wCAAwC;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,aAAa,EAAE,aAAa,CAAC,kBAAkB,CA4B3D,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"EntityFormRenderer.d.ts","sourceRoot":"","sources":["../../../src/crud/components/EntityFormRenderer.tsx"],"names":[],"mappings":"AAsCA,OAAO,KAAK,EACV,uBAAuB,EACvB,YAAY,EAEb,MAAM,gBAAgB,CAAC;AAExB,YAAY,EAAE,uBAAuB,EAAE,CAAC;AAExC;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,EACxE,MAAM,EACN,QAAQ,EACR,CAAC,EACD,SAAc,EACd,UAAU,EACV,OAAe,EACf,aAAa,EACb,aAAyB,EACzB,mBAAmB,EACnB,sBAAkC,EAClC,iBAAiB,EACjB,UAAU,EACV,SAA6C,EAC7C,QAAiC,EACjC,MAAM,EAAE,cAAc,EACtB,UAAU,EACV,UAAU,EACV,WAAW,EACX,QAAQ,EACR,oBAA2B,EAC3B,qBAAqB,EACrB,kBAA0B,GAC3B,EAAE,uBAAuB,CAAC,CAAC,CAAC,2CA+a5B;AAED,eAAe,kBAAkB,CAAC"}
1
+ {"version":3,"file":"EntityFormRenderer.d.ts","sourceRoot":"","sources":["../../../src/crud/components/EntityFormRenderer.tsx"],"names":[],"mappings":"AAsCA,OAAO,KAAK,EACV,uBAAuB,EACvB,YAAY,EAEb,MAAM,gBAAgB,CAAC;AAExB,YAAY,EAAE,uBAAuB,EAAE,CAAC;AAExC;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,EACxE,MAAM,EACN,QAAQ,EACR,CAAC,EACD,SAAc,EACd,UAAU,EACV,OAAe,EACf,aAAa,EACb,aAAyB,EACzB,mBAAmB,EACnB,sBAAkC,EAClC,iBAAiB,EACjB,UAAU,EACV,SAA6C,EAC7C,QAAiC,EACjC,MAAM,EAAE,cAAc,EACtB,UAAU,EACV,UAAU,EACV,WAAW,EACX,QAAQ,EACR,oBAA2B,EAC3B,qBAAqB,EACrB,kBAA0B,GAC3B,EAAE,uBAAuB,CAAC,CAAC,CAAC,2CA+b5B;AAED,eAAe,kBAAkB,CAAC"}
@@ -13,7 +13,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
13
  import { useEffect, useId, useMemo, useState } from 'react';
14
14
  import { FormProvider } from 'react-hook-form';
15
15
  import { Badge, Button, DropdownMenu, Grid, Stack, Spinner, } from '@donotdev/components';
16
- import { useTranslation } from '@donotdev/core';
16
+ import { hasRoleAccess, useTranslation } from '@donotdev/core';
17
17
  import { useNavigate } from '../../routing';
18
18
  import { DisplayFieldRenderer, FormFieldRenderer, UploadProvider, useEntityForm, useUnsavedChangesWarning, useConfirmNavigation, useFormStore, } from '@donotdev/crud';
19
19
  /**
@@ -34,12 +34,19 @@ export function EntityFormRenderer({ entity, onSubmit, t, className = '', submit
34
34
  // Generate stable form ID
35
35
  const generatedFormId = useId();
36
36
  const formId = externalFormId ?? `entity-form-${entity.name}-${generatedFormId}`;
37
- // Use entity name as i18n namespace
38
- const { t: translationFn } = useTranslation(entity.namespace);
37
+ // Entity + crud namespaces so translateLabel can resolve crud:status.* etc.
38
+ const { t: translationFn } = useTranslation([entity.namespace, 'crud']);
39
39
  const { t: tCrud } = useTranslation('crud');
40
40
  const translate = t || translationFn;
41
- // Preview role state for View As toggle (only used when visibility info is shown)
42
- const [previewRole, setPreviewRole] = useState('super');
41
+ // Visibility badges (developer-facing) are for admins/super only
42
+ const isAdminOrSuper = viewerRole === 'admin' || viewerRole === 'super';
43
+ const showVisibilityBadges = !hideVisibilityInfo && isAdminOrSuper;
44
+ // Preview dropdown (View As) is available for user/admin/super
45
+ const canPreviewRole = viewerRole === 'user' || viewerRole === 'admin' || viewerRole === 'super';
46
+ // Preview role state for View As toggle
47
+ const [previewRole, setPreviewRole] = useState(viewerRole === 'admin' || viewerRole === 'super' || viewerRole === 'user'
48
+ ? viewerRole
49
+ : 'guest');
43
50
  // Visibility → Badge variant mapping (inline, no separate file needed)
44
51
  const visibilityBadgeVariant = {
45
52
  guest: 'muted',
@@ -47,6 +54,7 @@ export function EntityFormRenderer({ entity, onSubmit, t, className = '', submit
47
54
  admin: 'warning',
48
55
  super: 'destructive',
49
56
  technical: 'accent',
57
+ owner: 'secondary',
50
58
  };
51
59
  // Role hierarchy for filtering (higher index = more access)
52
60
  const roleHierarchy = ['guest', 'user', 'admin', 'super'];
@@ -58,12 +66,14 @@ export function EntityFormRenderer({ entity, onSubmit, t, className = '', submit
58
66
  super: 'Super',
59
67
  technical: 'System',
60
68
  };
61
- // Preview dropdown menu items
62
- const previewMenuItems = useMemo(() => roleHierarchy.map((role) => ({
69
+ // Preview dropdown menu items (roles current viewer can assume)
70
+ const previewMenuItems = useMemo(() => roleHierarchy
71
+ .filter((role) => hasRoleAccess(viewerRole, role))
72
+ .map((role) => ({
63
73
  label: roleLabels[role],
64
74
  checked: previewRole === role,
65
75
  onClick: () => setPreviewRole(role),
66
- })), [previewRole]);
76
+ })), [previewRole, viewerRole]);
67
77
  // useEntityForm handles all orchestration
68
78
  const form = useEntityForm(entity, {
69
79
  formId,
@@ -73,10 +83,11 @@ export function EntityFormRenderer({ entity, onSubmit, t, className = '', submit
73
83
  t: translate,
74
84
  autoSave,
75
85
  });
76
- const { control, handleSubmit, formState: { errors }, fields: rawFields, formStatus, uploadProgress, cleanup, isDirty, resetForm, } = form;
86
+ const { control, handleSubmit, formState: { errors }, fields: rawFields, formStatus, uploadProgress, cleanup, isDirty, hasUserInteracted, resetForm, } = form;
87
+ const isDirtyForBlocking = isDirty && hasUserInteracted;
77
88
  // Filter fields based on previewRole when visibility info is shown
78
89
  const renderableFields = useMemo(() => {
79
- if (hideVisibilityInfo)
90
+ if (!canPreviewRole)
80
91
  return rawFields;
81
92
  const previewRoleIndex = roleHierarchy.indexOf(previewRole);
82
93
  return rawFields.filter(({ config }) => {
@@ -89,13 +100,13 @@ export function EntityFormRenderer({ entity, onSubmit, t, className = '', submit
89
100
  const fieldRoleIndex = roleHierarchy.indexOf(fieldVisibility);
90
101
  return fieldRoleIndex <= previewRoleIndex;
91
102
  });
92
- }, [rawFields, hideVisibilityInfo, previewRole]);
103
+ }, [rawFields, canPreviewRole, previewRole]);
93
104
  // Sync isDirty to FormStore (single source of truth)
94
105
  useEffect(() => {
95
106
  if (formId) {
96
- useFormStore.getState().setIsDirty(formId, isDirty);
107
+ useFormStore.getState().setIsDirty(formId, isDirtyForBlocking);
97
108
  }
98
- }, [formId, isDirty]);
109
+ }, [formId, isDirtyForBlocking]);
99
110
  // Cleanup on unmount
100
111
  useEffect(() => {
101
112
  return cleanup;
@@ -112,15 +123,15 @@ export function EntityFormRenderer({ entity, onSubmit, t, className = '', submit
112
123
  // Warn about unsaved changes when navigating away (browser refresh/close)
113
124
  // SPA navigation blocking is handled by router hooks via FormStore
114
125
  useUnsavedChangesWarning({
115
- isDirty,
126
+ isDirty: isDirtyForBlocking,
116
127
  enabled: warnOnUnsavedChanges,
117
128
  message: unsavedChangesLeaveMessage,
118
129
  });
119
130
  // Use framework helper for cancel confirmation (DRY, SSR-safe)
120
- const confirmNavigation = useConfirmNavigation(isDirty, unsavedChangesDiscardMessage);
131
+ const confirmNavigation = useConfirmNavigation(isDirtyForBlocking, unsavedChangesDiscardMessage);
121
132
  // Handle cancel with confirmation
122
133
  const handleCancel = async () => {
123
- if (isDirty) {
134
+ if (isDirtyForBlocking) {
124
135
  const confirmed = await confirmNavigation();
125
136
  if (!confirmed)
126
137
  return;
@@ -212,8 +223,8 @@ export function EntityFormRenderer({ entity, onSubmit, t, className = '', submit
212
223
  gridColumn: '1 / -1',
213
224
  display: 'contents',
214
225
  }, children: [isLoading && _jsx(Spinner, { overlay: true }), _jsxs("form", { onSubmit: handleSubmit(handleFormSubmit), noValidate: true, className: className, style: { width: '100%', gridColumn: '1 / -1', display: 'contents' }, children: [renderableFields.map(({ name, config, editable }) => {
215
- // Show visibility badge for non-guest fields when visibility info is enabled
216
- const showBadge = !hideVisibilityInfo && config.visibility !== 'guest';
226
+ // Show visibility badge for non-guest fields when visibility badges are enabled (admins/super only)
227
+ const showBadge = showVisibilityBadges && config.visibility !== 'guest';
217
228
  const badgeVariant = config.visibility !== 'hidden'
218
229
  ? visibilityBadgeVariant[config.visibility]
219
230
  : undefined;
@@ -239,8 +250,8 @@ export function EntityFormRenderer({ entity, onSubmit, t, className = '', submit
239
250
  if (showCancelButton && cancelButtonText) {
240
251
  buttons.push(_jsx(Button, { type: "button", onClick: handleCancel, disabled: buttonState.loading, variant: "outline", className: "dndev-w-full", children: cancelButtonText }, "cancel"));
241
252
  }
242
- // Preview dropdown
243
- if (!hideVisibilityInfo) {
253
+ // Preview dropdown (user/admin/super only)
254
+ if (canPreviewRole) {
244
255
  const previewVariant = visibilityBadgeVariant[previewRole] || 'muted';
245
256
  buttons.push(_jsx(DropdownMenu, { trigger: _jsxs(Button, { type: "button", variant: previewVariant, disabled: buttonState.loading, className: "dndev-w-full", children: [tCrud('visibility.preview', {
246
257
  defaultValue: 'Preview',
@@ -10,5 +10,5 @@ export type { EntityListProps };
10
10
  * - Edit and Delete actions (admin only)
11
11
  * - Auto-routing when handlers not provided
12
12
  */
13
- export declare function EntityList({ entity, userRole, basePath, onClick, hideFilters, pagination, pageSize: pageSizeProp, queryOptions, }: EntityListProps): import("react/jsx-runtime").JSX.Element;
13
+ export declare function EntityList({ entity, userRole, basePath, onClick, hideFilters, pagination, pageSize: pageSizeProp, queryOptions, exportable, }: EntityListProps): import("react/jsx-runtime").JSX.Element;
14
14
  //# sourceMappingURL=EntityList.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"EntityList.d.ts","sourceRoot":"","sources":["../../../src/crud/components/EntityList.tsx"],"names":[],"mappings":"AAyCA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEtD,YAAY,EAAE,eAAe,EAAE,CAAC;AAEhC;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,EACzB,MAAM,EACN,QAAkB,EAClB,QAAQ,EACR,OAAO,EACP,WAAmB,EACnB,UAAqB,EACrB,QAAQ,EAAE,YAAY,EACtB,YAAY,GACb,EAAE,eAAe,2CAsTjB"}
1
+ {"version":3,"file":"EntityList.d.ts","sourceRoot":"","sources":["../../../src/crud/components/EntityList.tsx"],"names":[],"mappings":"AAyCA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEtD,YAAY,EAAE,eAAe,EAAE,CAAC;AAEhC;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,EACzB,MAAM,EACN,QAAkB,EAClB,QAAQ,EACR,OAAO,EACP,WAAmB,EACnB,UAAqB,EACrB,QAAQ,EAAE,YAAY,EACtB,YAAY,EACZ,UAAiB,GAClB,EAAE,eAAe,2CA0TjB"}
@@ -29,7 +29,7 @@ import { translateFieldLabel, useCrud, useCrudList, EntityFilters, matchesFilter
29
29
  * - Edit and Delete actions (admin only)
30
30
  * - Auto-routing when handlers not provided
31
31
  */
32
- export function EntityList({ entity, userRole = 'guest', basePath, onClick, hideFilters = false, pagination = 'client', pageSize: pageSizeProp, queryOptions, }) {
32
+ export function EntityList({ entity, userRole = 'guest', basePath, onClick, hideFilters = false, pagination = 'client', pageSize: pageSizeProp, queryOptions, exportable = true, }) {
33
33
  const navigate = useNavigate();
34
34
  const base = basePath ?? `/${entity.collection}`;
35
35
  // Server-side pagination state (only used when pagination='server')
@@ -197,5 +197,7 @@ export function EntityList({ entity, userRole = 'guest', basePath, onClick, hide
197
197
  total: listData?.total,
198
198
  onPageChange: setCurrentPage,
199
199
  onPageSizeChange: setServerPageSize,
200
- }) }) })] }));
200
+ }),
201
+ // Export functionality
202
+ exportable: exportable, exportFilename: `${entity.collection}-export`, exportLabel: tCrud('export', { defaultValue: 'Export' }) }) })] }));
201
203
  }