@donotdev/ui 0.0.10 → 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.
Files changed (109) hide show
  1. package/dist/components/layout/GameContainer.d.ts +3 -1
  2. package/dist/components/layout/GameContainer.d.ts.map +1 -1
  3. package/dist/components/layout/GameContainer.js +3 -2
  4. package/dist/components/layout/PageContainer.d.ts +1 -1
  5. package/dist/components/layout/PageContainer.d.ts.map +1 -1
  6. package/dist/crud/components/EntityCardList.d.ts +16 -0
  7. package/dist/crud/components/EntityCardList.d.ts.map +1 -0
  8. package/dist/crud/components/EntityCardList.js +175 -0
  9. package/dist/crud/components/EntityDisplayRenderer.d.ts +13 -21
  10. package/dist/crud/components/EntityDisplayRenderer.d.ts.map +1 -1
  11. package/dist/crud/components/EntityDisplayRenderer.js +138 -23
  12. package/dist/crud/components/EntityFormRenderer.d.ts +18 -0
  13. package/dist/crud/components/EntityFormRenderer.d.ts.map +1 -0
  14. package/dist/crud/components/EntityFormRenderer.js +286 -0
  15. package/dist/crud/components/EntityList.d.ts +14 -0
  16. package/dist/crud/components/EntityList.d.ts.map +1 -0
  17. package/dist/crud/components/EntityList.js +203 -0
  18. package/dist/crud/components/index.d.ts +7 -5
  19. package/dist/crud/components/index.d.ts.map +1 -1
  20. package/dist/crud/components/index.js +6 -5
  21. package/dist/dndev.css +287 -123
  22. package/dist/index.js +4 -64
  23. package/dist/internal/layout/components/AutoMetaTags.d.ts.map +1 -1
  24. package/dist/internal/layout/components/AutoMetaTags.js +36 -6
  25. package/dist/internal/layout/components/NextJsAutoMetaTags.d.ts.map +1 -1
  26. package/dist/internal/layout/components/NextJsAutoMetaTags.js +38 -10
  27. package/dist/internal/layout/components/footer/FooterBranding.js +2 -2
  28. package/dist/internal/layout/config/presets/game.d.ts.map +1 -1
  29. package/dist/internal/layout/config/presets/game.js +10 -5
  30. package/dist/styles/index.css +287 -123
  31. package/package.json +13 -13
  32. package/dist/crud/components/DisplayFieldRenderer.d.ts +0 -26
  33. package/dist/crud/components/DisplayFieldRenderer.d.ts.map +0 -1
  34. package/dist/crud/components/DisplayFieldRenderer.js +0 -107
  35. package/dist/crud/components/fields/display/AvatarFieldDisplay.d.ts +0 -23
  36. package/dist/crud/components/fields/display/AvatarFieldDisplay.d.ts.map +0 -1
  37. package/dist/crud/components/fields/display/AvatarFieldDisplay.js +0 -38
  38. package/dist/crud/components/fields/display/BadgeFieldDisplay.d.ts +0 -21
  39. package/dist/crud/components/fields/display/BadgeFieldDisplay.d.ts.map +0 -1
  40. package/dist/crud/components/fields/display/BadgeFieldDisplay.js +0 -31
  41. package/dist/crud/components/fields/display/ButtonFieldDisplay.d.ts +0 -29
  42. package/dist/crud/components/fields/display/ButtonFieldDisplay.d.ts.map +0 -1
  43. package/dist/crud/components/fields/display/ButtonFieldDisplay.js +0 -12
  44. package/dist/crud/components/fields/display/CheckboxFieldDisplay.d.ts +0 -21
  45. package/dist/crud/components/fields/display/CheckboxFieldDisplay.d.ts.map +0 -1
  46. package/dist/crud/components/fields/display/CheckboxFieldDisplay.js +0 -27
  47. package/dist/crud/components/fields/display/DateFieldDisplay.d.ts +0 -24
  48. package/dist/crud/components/fields/display/DateFieldDisplay.d.ts.map +0 -1
  49. package/dist/crud/components/fields/display/DateFieldDisplay.js +0 -41
  50. package/dist/crud/components/fields/display/DropdownDisplay.d.ts +0 -21
  51. package/dist/crud/components/fields/display/DropdownDisplay.d.ts.map +0 -1
  52. package/dist/crud/components/fields/display/DropdownDisplay.js +0 -25
  53. package/dist/crud/components/fields/display/FileFieldDisplay.d.ts +0 -21
  54. package/dist/crud/components/fields/display/FileFieldDisplay.d.ts.map +0 -1
  55. package/dist/crud/components/fields/display/FileFieldDisplay.js +0 -25
  56. package/dist/crud/components/fields/display/GeoPointFieldDisplay.d.ts +0 -25
  57. package/dist/crud/components/fields/display/GeoPointFieldDisplay.d.ts.map +0 -1
  58. package/dist/crud/components/fields/display/GeoPointFieldDisplay.js +0 -25
  59. package/dist/crud/components/fields/display/HiddenFieldDisplay.d.ts +0 -30
  60. package/dist/crud/components/fields/display/HiddenFieldDisplay.d.ts.map +0 -1
  61. package/dist/crud/components/fields/display/HiddenFieldDisplay.js +0 -12
  62. package/dist/crud/components/fields/display/ImageFieldDisplay.d.ts +0 -24
  63. package/dist/crud/components/fields/display/ImageFieldDisplay.d.ts.map +0 -1
  64. package/dist/crud/components/fields/display/ImageFieldDisplay.js +0 -38
  65. package/dist/crud/components/fields/display/LinkFieldDisplay.d.ts +0 -22
  66. package/dist/crud/components/fields/display/LinkFieldDisplay.d.ts.map +0 -1
  67. package/dist/crud/components/fields/display/LinkFieldDisplay.js +0 -48
  68. package/dist/crud/components/fields/display/MapFieldDisplay.d.ts +0 -25
  69. package/dist/crud/components/fields/display/MapFieldDisplay.d.ts.map +0 -1
  70. package/dist/crud/components/fields/display/MapFieldDisplay.js +0 -25
  71. package/dist/crud/components/fields/display/MultiDropdownDisplay.d.ts +0 -22
  72. package/dist/crud/components/fields/display/MultiDropdownDisplay.d.ts.map +0 -1
  73. package/dist/crud/components/fields/display/MultiDropdownDisplay.js +0 -25
  74. package/dist/crud/components/fields/display/MultiInputTextFieldDisplay.d.ts +0 -22
  75. package/dist/crud/components/fields/display/MultiInputTextFieldDisplay.d.ts.map +0 -1
  76. package/dist/crud/components/fields/display/MultiInputTextFieldDisplay.js +0 -25
  77. package/dist/crud/components/fields/display/NumberFieldDisplay.d.ts +0 -24
  78. package/dist/crud/components/fields/display/NumberFieldDisplay.d.ts.map +0 -1
  79. package/dist/crud/components/fields/display/NumberFieldDisplay.js +0 -28
  80. package/dist/crud/components/fields/display/PasswordFieldDisplay.d.ts +0 -24
  81. package/dist/crud/components/fields/display/PasswordFieldDisplay.d.ts.map +0 -1
  82. package/dist/crud/components/fields/display/PasswordFieldDisplay.js +0 -31
  83. package/dist/crud/components/fields/display/PhoneNumberDisplay.d.ts +0 -22
  84. package/dist/crud/components/fields/display/PhoneNumberDisplay.d.ts.map +0 -1
  85. package/dist/crud/components/fields/display/PhoneNumberDisplay.js +0 -25
  86. package/dist/crud/components/fields/display/RadioFieldDisplay.d.ts +0 -22
  87. package/dist/crud/components/fields/display/RadioFieldDisplay.d.ts.map +0 -1
  88. package/dist/crud/components/fields/display/RadioFieldDisplay.js +0 -25
  89. package/dist/crud/components/fields/display/RangeFieldDisplay.d.ts +0 -22
  90. package/dist/crud/components/fields/display/RangeFieldDisplay.d.ts.map +0 -1
  91. package/dist/crud/components/fields/display/RangeFieldDisplay.js +0 -25
  92. package/dist/crud/components/fields/display/ReferenceFieldDisplay.d.ts +0 -22
  93. package/dist/crud/components/fields/display/ReferenceFieldDisplay.d.ts.map +0 -1
  94. package/dist/crud/components/fields/display/ReferenceFieldDisplay.js +0 -26
  95. package/dist/crud/components/fields/display/RichTextDisplay.d.ts +0 -25
  96. package/dist/crud/components/fields/display/RichTextDisplay.d.ts.map +0 -1
  97. package/dist/crud/components/fields/display/RichTextDisplay.js +0 -104
  98. package/dist/crud/components/fields/display/TextAreaDisplay.d.ts +0 -22
  99. package/dist/crud/components/fields/display/TextAreaDisplay.d.ts.map +0 -1
  100. package/dist/crud/components/fields/display/TextAreaDisplay.js +0 -25
  101. package/dist/crud/components/fields/display/TextFieldDisplay.d.ts +0 -42
  102. package/dist/crud/components/fields/display/TextFieldDisplay.d.ts.map +0 -1
  103. package/dist/crud/components/fields/display/TextFieldDisplay.js +0 -97
  104. package/dist/crud/components/fields/display/TimestampFieldDisplay.d.ts +0 -22
  105. package/dist/crud/components/fields/display/TimestampFieldDisplay.d.ts.map +0 -1
  106. package/dist/crud/components/fields/display/TimestampFieldDisplay.js +0 -33
  107. package/dist/crud/components/fields/display/index.d.ts +0 -32
  108. package/dist/crud/components/fields/display/index.d.ts.map +0 -1
  109. package/dist/crud/components/fields/display/index.js +0 -32
@@ -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"}
@@ -0,0 +1,16 @@
1
+ import type { EntityCardListProps } from '@donotdev/core';
2
+ export type { EntityCardListProps };
3
+ /**
4
+ * Entity Card List Component - Card grid view for public/user-facing browsing
5
+ *
6
+ * Features:
7
+ * - Responsive card grid layout
8
+ * - Image + key fields display
9
+ * - Click card to navigate to detail
10
+ * - Simple formatted text display (labels + values)
11
+ * - Empty state handling
12
+ * - Auto-routing when handler not provided
13
+ */
14
+ export declare function EntityCardList({ entity, basePath, onClick, cols, staleTime, // 30 minutes default cache
15
+ filter, hideFilters, }: EntityCardListProps): import("react/jsx-runtime").JSX.Element;
16
+ //# sourceMappingURL=EntityCardList.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EntityCardList.d.ts","sourceRoot":"","sources":["../../../src/crud/components/EntityCardList.tsx"],"names":[],"mappings":"AAwCA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE1D,YAAY,EAAE,mBAAmB,EAAE,CAAC;AAEpC;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,QAAQ,EACR,OAAO,EACP,IAAmB,EACnB,SAA0B,EAAE,2BAA2B;AACvD,MAAM,EACN,WAAmB,GACpB,EAAE,mBAAmB,2CA0RrB"}
@@ -0,0 +1,175 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ // packages/ui/src/crud/components/EntityCardList.tsx
4
+ /**
5
+ * @fileoverview Entity Card List Component
6
+ * @description Card grid view for public/user-facing entity browsing.
7
+ * Features: Responsive grid of cards with images, key fields, and navigation to detail pages.
8
+ *
9
+ * **Routing:** Convention basePath = `/${collection}`. View = basePath/:id.
10
+ * Override basePath for nested routes; use onClick(id) to open sheet instead of navigating.
11
+ *
12
+ * @version 0.2.0
13
+ * @since 0.0.1
14
+ * @author AMBROISE PARK Consulting
15
+ */
16
+ import { useMemo, useCallback, useState } from 'react';
17
+ import { Heart } from 'lucide-react';
18
+ import { Grid, Card, Stack, Text, Spinner, Section, Button, } from '@donotdev/components';
19
+ import { useTranslation } from '@donotdev/core';
20
+ import { useNavigate } from '../../routing';
21
+ import { translateFieldLabel, useCrudCardList, EntityFilters, useEntityFavorites, matchesFilter, formatValue, } from '@donotdev/crud';
22
+ /**
23
+ * Entity Card List Component - Card grid view for public/user-facing browsing
24
+ *
25
+ * Features:
26
+ * - Responsive card grid layout
27
+ * - Image + key fields display
28
+ * - Click card to navigate to detail
29
+ * - Simple formatted text display (labels + values)
30
+ * - Empty state handling
31
+ * - Auto-routing when handler not provided
32
+ */
33
+ export function EntityCardList({ entity, basePath, onClick, cols = [1, 2, 3, 4], staleTime = 1000 * 60 * 30, // 30 minutes default cache
34
+ filter, hideFilters = false, }) {
35
+ const navigate = useNavigate();
36
+ const base = basePath ?? `/${entity.collection}`;
37
+ // useCrudCardList -> handles fetching optimized for cards automatically
38
+ const { data: listData, loading } = useCrudCardList(entity, { staleTime });
39
+ const rawData = listData?.items || [];
40
+ // Favorites - always enabled, no props needed
41
+ const { isFavorite, toggleFavorite, favoritesFilter } = useEntityFavorites({
42
+ collection: entity.collection,
43
+ });
44
+ const [showFavoritesOnly, setShowFavoritesOnly] = useState(false);
45
+ // Filter state
46
+ const [filters, setFilters] = useState({});
47
+ // Apply filters from EntityFilters component
48
+ const applyFilters = useCallback((item) => {
49
+ if (Object.keys(filters).length === 0)
50
+ return true;
51
+ return Object.entries(filters).every(([fieldName, filterValue]) => {
52
+ const itemValue = item[fieldName];
53
+ const fieldConfig = entity.fields[fieldName];
54
+ const fieldType = fieldConfig?.type || 'text';
55
+ return matchesFilter(itemValue, filterValue, fieldType);
56
+ });
57
+ }, [filters, entity.fields]);
58
+ // Apply client-side filtering (including favorites)
59
+ const data = useMemo(() => {
60
+ let result = rawData;
61
+ result = result.filter(applyFilters);
62
+ // Apply favorites filter if enabled
63
+ if (showFavoritesOnly) {
64
+ result = result.filter(favoritesFilter);
65
+ }
66
+ if (filter) {
67
+ result = result.filter(filter);
68
+ }
69
+ return result;
70
+ }, [rawData, applyFilters, showFavoritesOnly, favoritesFilter, filter]);
71
+ // Entity + crud namespaces so formatValue can resolve crud:price.* etc.
72
+ const { t } = useTranslation([entity.namespace, 'crud']);
73
+ const { t: tCrud } = useTranslation('crud');
74
+ // Card click: onClick(id) if provided, else navigate to basePath/:id
75
+ const handleView = useCallback((id) => {
76
+ if (onClick) {
77
+ onClick(id);
78
+ }
79
+ else {
80
+ navigate(`${base}/${id}`);
81
+ }
82
+ }, [base, navigate, onClick]);
83
+ // Determine which fields to show in cards
84
+ const fieldsToShow = useMemo(() => {
85
+ const cardFields = entity.listCardFields ?? entity.listFields;
86
+ if (cardFields && cardFields.length > 0)
87
+ return cardFields;
88
+ return Object.keys(entity.fields).slice(0, 4);
89
+ }, [entity.listCardFields, entity.listFields, entity.fields]);
90
+ // Find image field
91
+ const imageField = useMemo(() => {
92
+ const imageFieldsInList = fieldsToShow.filter((fieldName) => {
93
+ const fieldConfig = entity.fields[fieldName];
94
+ return fieldConfig?.type === 'image' || fieldConfig?.type === 'images';
95
+ });
96
+ if (imageFieldsInList.length > 0)
97
+ return imageFieldsInList[0];
98
+ // Fallback: search all entity fields
99
+ const allImageFields = Object.keys(entity.fields).filter((fieldName) => {
100
+ const fieldConfig = entity.fields[fieldName];
101
+ return fieldConfig?.type === 'image' || fieldConfig?.type === 'images';
102
+ });
103
+ return allImageFields[0] || null;
104
+ }, [fieldsToShow, entity.fields]);
105
+ // Get other fields (non-image)
106
+ const otherFields = useMemo(() => {
107
+ return fieldsToShow.filter((fieldName) => fieldName !== imageField);
108
+ }, [fieldsToShow, imageField]);
109
+ const entityName = t('name', { defaultValue: entity.name });
110
+ return (_jsxs(_Fragment, { children: [!hideFilters && (_jsx(Section, { title: tCrud('filters.title', {
111
+ entity: entityName,
112
+ defaultValue: `Browse ${entityName} - Filters`,
113
+ }), collapsible: true, defaultOpen: true, children: _jsxs(Stack, { direction: "column", gap: "medium", children: [_jsx(Button, { variant: showFavoritesOnly ? 'primary' : 'outline', icon: _jsx(Heart, { size: 18 }), onClick: () => setShowFavoritesOnly(!showFavoritesOnly), children: showFavoritesOnly
114
+ ? tCrud('favorites.showAll', { defaultValue: 'Show All' })
115
+ : tCrud('favorites.showFavorites', {
116
+ defaultValue: 'Show Favorites',
117
+ }) }), _jsx(EntityFilters, { entity: entity, data: rawData, filters: filters, onFiltersChange: setFilters, fieldsToFilter: entity.listCardFields })] }) })), _jsx(Section, { title: loading
118
+ ? tCrud('results.title.fetching', { defaultValue: 'Fetching...' })
119
+ : tCrud('results.title.count', {
120
+ count: data.length,
121
+ defaultValue: data.length === 1
122
+ ? 'Found 1 occurrence'
123
+ : `Found ${data.length} occurrences`,
124
+ }), collapsible: true, defaultOpen: true, children: loading ? (_jsx(Stack, { align: "center", justify: "center", gap: "medium", style: { padding: 'var(--gap-3xl)' }, children: _jsx(Spinner, {}) })) : data.length === 0 ? (_jsxs(Stack, { align: "center", justify: "center", gap: "medium", style: { padding: 'var(--gap-3xl)', textAlign: 'center' }, children: [_jsx(Text, { level: "h3", style: { color: 'var(--muted-foreground)' }, children: tCrud('emptyState.title', {
125
+ defaultValue: `No ${entity.name.toLowerCase()} found`,
126
+ }) }), _jsx(Text, { style: { color: 'var(--muted-foreground)' }, children: tCrud('emptyState.description', {
127
+ defaultValue: `No ${entity.name.toLowerCase()} available at this time.`,
128
+ }) })] })) : (_jsx(Grid, { cols: cols, gap: "medium", children: data.map((item) => {
129
+ const imageValue = imageField ? item[imageField] : null;
130
+ // Backend optimizes picture fields for listCard: returns thumbUrl string directly
131
+ // (or fullUrl if thumbUrl missing, or null if no picture)
132
+ const imageUrl = typeof imageValue === 'string' ? imageValue : null;
133
+ // Title from first non-image field
134
+ const titleField = otherFields[0];
135
+ const titleValue = titleField ? item[titleField] : item.id;
136
+ const itemIsFavorite = isFavorite(item.id);
137
+ return (_jsxs(Card, { title: String(titleValue || ''), clickable: true, onClick: () => handleView(item.id), elevated: true, children: [_jsx(Heart, { fill: itemIsFavorite ? '#ef4444' : '#ffffff', stroke: itemIsFavorite ? '#ef4444' : 'var(--muted-foreground)', onClick: (e) => {
138
+ e.stopPropagation();
139
+ toggleFavorite(item.id);
140
+ }, style: {
141
+ position: 'absolute',
142
+ top: 'var(--gap-sm)',
143
+ right: 'var(--gap-sm)',
144
+ zIndex: 10,
145
+ cursor: 'pointer',
146
+ width: 'var(--icon-md)',
147
+ height: 'var(--icon-md)',
148
+ transition: 'fill 0.2s, stroke 0.2s',
149
+ }, "aria-label": itemIsFavorite
150
+ ? tCrud('favorites.remove', {
151
+ defaultValue: 'Remove from favorites',
152
+ })
153
+ : tCrud('favorites.add', {
154
+ defaultValue: 'Add to favorites',
155
+ }) }), _jsxs(Stack, { direction: "column", gap: "medium", children: [imageUrl && (_jsx("div", { style: {
156
+ width: '100%',
157
+ aspectRatio: '16/9',
158
+ borderRadius: 'var(--radius-md)',
159
+ overflow: 'hidden',
160
+ backgroundColor: 'var(--muted)',
161
+ position: 'relative',
162
+ }, children: _jsx("img", { src: imageUrl, alt: String(titleValue || ''), style: {
163
+ width: '100%',
164
+ height: '100%',
165
+ objectFit: 'cover',
166
+ } }) })), _jsx(Stack, { direction: "column", gap: "tight", children: otherFields.slice(1, 4).map((fieldName) => {
167
+ const fieldConfig = entity.fields[fieldName];
168
+ if (!fieldConfig)
169
+ return null;
170
+ return (_jsxs("div", { children: [_jsx(Text, { level: "small", variant: "muted", children: translateFieldLabel(fieldName, fieldConfig, t) }), _jsx(Text, { children: formatValue(item[fieldName], fieldConfig, t, {
171
+ compact: true,
172
+ }) })] }, fieldName));
173
+ }) })] })] }, item.id));
174
+ }) })) })] }));
175
+ }
@@ -1,29 +1,21 @@
1
- import type { EntityField } from '@donotdev/core';
2
- interface EntityDisplayRendererProps<T extends Record<string, any> = any> {
3
- /** Entity definition with field configs */
4
- entity: Record<string, EntityField>;
5
- /** Entity data to display */
6
- data: T;
7
- /** Translation function */
8
- t?: (key: string, options?: Record<string, any>) => string;
9
- /** Additional CSS classes */
10
- className?: string;
11
- /** Whether data is loading */
12
- loading?: boolean;
13
- }
1
+ import type { EntityDisplayRendererProps, EntityRecord } from '@donotdev/core';
2
+ export type { EntityDisplayRendererProps };
14
3
  /**
15
- * EntityDisplayRenderer - High-level component that renders read-only display from an entity definition
4
+ * EntityDisplayRenderer - Automatically fetches and displays entity data
16
5
  *
17
6
  * Features:
18
- * - Loops through entity fields and renders each with DisplayFieldRenderer
19
- * - Automatic loading states
7
+ * - Automatic data fetching using useCrud
8
+ * - Loading state handling
9
+ * - Error handling
10
+ * - Automatic field rendering (respects visibility rules)
20
11
  * - Full i18n support
21
- * - Consistent styling
12
+ * - No form/submit buttons - pure read-only display
22
13
  *
23
- * @version 0.0.1
24
- * @since 0.0.1
25
- * @author AMBROISE PARK Consulting
14
+ * @example
15
+ * ```tsx
16
+ * <EntityDisplayRenderer entity={carEntity} id={carId} />
17
+ * ```
26
18
  */
27
- export declare function EntityDisplayRenderer<T extends Record<string, any> = any>({ entity, data, t, className, loading, }: EntityDisplayRendererProps<T>): import("react/jsx-runtime").JSX.Element;
19
+ export declare function EntityDisplayRenderer<T extends EntityRecord = EntityRecord>({ entity, id, t, className, backend, loadingMessage, notFoundMessage, viewerRole, }: EntityDisplayRendererProps<T>): import("react/jsx-runtime").JSX.Element;
28
20
  export default EntityDisplayRenderer;
29
21
  //# sourceMappingURL=EntityDisplayRenderer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"EntityDisplayRenderer.d.ts","sourceRoot":"","sources":["../../../src/crud/components/EntityDisplayRenderer.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAIlD,UAAU,0BAA0B,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG;IACtE,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACpC,6BAA6B;IAC7B,IAAI,EAAE,CAAC,CAAC;IACR,2BAA2B;IAC3B,CAAC,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,MAAM,CAAC;IAC3D,6BAA6B;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8BAA8B;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,EACzE,MAAM,EACN,IAAI,EACJ,CAAC,EACD,SAAc,EACd,OAAe,GAChB,EAAE,0BAA0B,CAAC,CAAC,CAAC,2CAyC/B;AAED,eAAe,qBAAqB,CAAC"}
1
+ {"version":3,"file":"EntityDisplayRenderer.d.ts","sourceRoot":"","sources":["../../../src/crud/components/EntityDisplayRenderer.tsx"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,0BAA0B,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAG/E,YAAY,EAAE,0BAA0B,EAAE,CAAC;AAE3C;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,YAAY,GAAG,YAAY,EAAE,EAC3E,MAAM,EACN,EAAE,EACF,CAAC,EACD,SAAc,EACd,OAAqB,EACrB,cAAc,EACd,eAAe,EACf,UAAoB,GACrB,EAAE,0BAA0B,CAAC,CAAC,CAAC,2CAgM/B;AAED,eAAe,qBAAqB,CAAC"}
@@ -3,39 +3,154 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  // packages/ui/src/crud/components/EntityDisplayRenderer.tsx
4
4
  /**
5
5
  * @fileoverview EntityDisplayRenderer component
6
- * @description High-level component that renders read-only display from an entity definition
6
+ * @description High-level component that automatically fetches and displays entity data in read-only mode.
7
+ * Perfect for detail/view pages - just pass the entity and it handles everything.
7
8
  *
8
- * @version 0.0.1
9
+ * @version 0.0.2
9
10
  * @since 0.0.1
10
11
  * @author AMBROISE PARK Consulting
11
12
  */
12
- import { Stack } from '@donotdev/components';
13
- import { useTranslation } from '@donotdev/core';
14
- import { DisplayFieldRenderer } from './DisplayFieldRenderer';
13
+ import { useEffect, useState, useMemo } from 'react';
14
+ import { Stack, Spinner } from '@donotdev/components';
15
+ import { useTranslation, isFieldVisible } from '@donotdev/core';
16
+ import { useCrud, DisplayFieldRenderer } from '@donotdev/crud';
15
17
  /**
16
- * EntityDisplayRenderer - High-level component that renders read-only display from an entity definition
18
+ * EntityDisplayRenderer - Automatically fetches and displays entity data
17
19
  *
18
20
  * Features:
19
- * - Loops through entity fields and renders each with DisplayFieldRenderer
20
- * - Automatic loading states
21
+ * - Automatic data fetching using useCrud
22
+ * - Loading state handling
23
+ * - Error handling
24
+ * - Automatic field rendering (respects visibility rules)
21
25
  * - Full i18n support
22
- * - Consistent styling
26
+ * - No form/submit buttons - pure read-only display
23
27
  *
24
- * @version 0.0.1
25
- * @since 0.0.1
26
- * @author AMBROISE PARK Consulting
28
+ * @example
29
+ * ```tsx
30
+ * <EntityDisplayRenderer entity={carEntity} id={carId} />
31
+ * ```
27
32
  */
28
- export function EntityDisplayRenderer({ entity, data, t, className = '', loading = false, }) {
29
- const { t: translationFn } = useTranslation('dndev');
30
- const translate = t || translationFn;
31
- const entityFields = Object.entries(entity);
32
- if (loading) {
33
- return (_jsx(Stack, { gap: "medium", className: className, children: entityFields.map(([fieldName]) => (_jsxs("div", { className: "dndev-animate-pulse", children: [_jsx("div", { style: {
34
- marginBottom: 'var(--gap-sm)',
35
- height: '1rem',
36
- width: '25%',
37
- } }), _jsx("div", { style: { height: '2rem', backgroundColor: 'var(--muted)' } })] }, fieldName))) }));
33
+ export function EntityDisplayRenderer({ entity, id, t, className = '', backend = 'functions', loadingMessage, notFoundMessage, viewerRole = 'guest', }) {
34
+ const { get, loading, data: storeData, error: storeError, isAvailable, } = useCrud(entity, { backend });
35
+ const [isFetching, setIsFetching] = useState(false);
36
+ const [fetchError, setFetchError] = useState(null);
37
+ const [data, setData] = useState(null);
38
+ // Entity + crud namespaces so formatValue can resolve crud:price.* etc.
39
+ const { t: tDual } = useTranslation([entity.namespace, 'crud']);
40
+ const { t: tCrud } = useTranslation('crud');
41
+ const translate = t || tDual;
42
+ // Fetch data when id is available and service is ready
43
+ useEffect(() => {
44
+ // Early return if no ID
45
+ if (!id) {
46
+ setData(null);
47
+ setFetchError(null);
48
+ setIsFetching(false);
49
+ return;
50
+ }
51
+ // Early return if service not ready
52
+ if (!isAvailable || !get) {
53
+ return;
54
+ }
55
+ let cancelled = false;
56
+ setIsFetching(true);
57
+ setFetchError(null);
58
+ // Call get function - it should trigger the fetch
59
+ get(id)
60
+ .then((fetchedData) => {
61
+ if (cancelled)
62
+ return;
63
+ setIsFetching(false);
64
+ if (fetchedData) {
65
+ setData(fetchedData);
66
+ setFetchError(null);
67
+ }
68
+ else {
69
+ setData(null);
70
+ setFetchError(new Error('Entity not found'));
71
+ }
72
+ })
73
+ .catch((err) => {
74
+ if (cancelled)
75
+ return;
76
+ setIsFetching(false);
77
+ setFetchError(err instanceof Error ? err : new Error(String(err)));
78
+ setData(null);
79
+ });
80
+ return () => {
81
+ cancelled = true;
82
+ };
83
+ }, [id, get, isAvailable]);
84
+ // Use store data if available, otherwise use local state
85
+ const displayData = storeData || data;
86
+ const displayError = storeError || fetchError;
87
+ const isLoading = loading || isFetching || !id;
88
+ // Filter fields: respect visibility AND skip empty values
89
+ // MUST be called before any early returns (Rules of Hooks)
90
+ const visibleFields = useMemo(() => {
91
+ if (!displayData)
92
+ return [];
93
+ return Object.entries(entity.fields).filter(([fieldName, fieldConfig]) => {
94
+ // Check visibility first
95
+ if (!isFieldVisible(fieldConfig.visibility, viewerRole)) {
96
+ return false;
97
+ }
98
+ // Skip hidden fields
99
+ if (fieldConfig.visibility === 'hidden') {
100
+ return false;
101
+ }
102
+ // Get field value
103
+ const value = displayData[fieldName];
104
+ // Hide if value is empty/null/undefined
105
+ if (value === null || value === undefined) {
106
+ return false;
107
+ }
108
+ // Hide empty strings
109
+ if (typeof value === 'string' && value.trim() === '') {
110
+ return false;
111
+ }
112
+ // Hide empty arrays
113
+ if (Array.isArray(value) && value.length === 0) {
114
+ return false;
115
+ }
116
+ // Hide empty objects (but allow objects with properties)
117
+ if (typeof value === 'object' &&
118
+ !Array.isArray(value) &&
119
+ value !== null &&
120
+ Object.keys(value).length === 0) {
121
+ return false;
122
+ }
123
+ return true;
124
+ });
125
+ }, [entity.fields, viewerRole, displayData]);
126
+ // Loading state
127
+ if (isLoading) {
128
+ return (_jsx("div", { style: {
129
+ position: 'relative',
130
+ width: '100%',
131
+ gridColumn: '1 / -1',
132
+ display: 'contents',
133
+ }, className: className, children: _jsx(Spinner, { overlay: true, "aria-label": loadingMessage ||
134
+ tCrud('form.loading', { defaultValue: 'Loading...' }) }) }));
135
+ }
136
+ // Error or not found state
137
+ if (displayError || !displayData) {
138
+ return (_jsx(Stack, { align: "center", justify: "center", gap: "medium", style: {
139
+ padding: 'var(--gap-3xl)',
140
+ textAlign: 'center',
141
+ }, className: className, children: _jsxs(Stack, { direction: "column", gap: "tight", children: [_jsx("h3", { style: { color: 'var(--muted-foreground)' }, children: notFoundMessage ||
142
+ tCrud('errors.notFound', {
143
+ defaultValue: `${entity.name} not found`,
144
+ }) }), displayError && (_jsx("p", { style: {
145
+ color: 'var(--destructive)',
146
+ fontSize: 'var(--font-size-sm)',
147
+ }, children: displayError instanceof Error
148
+ ? displayError.message
149
+ : String(displayError) }))] }) }));
38
150
  }
39
- return (_jsx(Stack, { gap: "medium", className: className, children: entityFields.map(([fieldName, fieldConfig]) => (_jsx(DisplayFieldRenderer, { name: fieldName, config: fieldConfig, value: data[fieldName], t: translate }, fieldName))) }));
151
+ // Render all visible fields with values
152
+ return (_jsx(Stack, { direction: "column", gap: "medium", className: className, children: visibleFields.map(([fieldName, fieldConfig]) => {
153
+ return (_jsx(DisplayFieldRenderer, { name: fieldName, config: fieldConfig, value: displayData[fieldName], t: translate }, fieldName));
154
+ }) }));
40
155
  }
41
156
  export default EntityDisplayRenderer;
@@ -0,0 +1,18 @@
1
+ import type { EntityFormRendererProps, EntityRecord } from '@donotdev/core';
2
+ export type { EntityFormRendererProps };
3
+ /**
4
+ * EntityFormRenderer - Dumb component that renders a form from entity definition.
5
+ *
6
+ * All orchestration (uploads, validation, status tracking) is handled by useEntityForm.
7
+ * This component just:
8
+ * - Generates formId
9
+ * - Renders fields
10
+ * - Renders submit button with status from useEntityForm
11
+ *
12
+ * @version 0.0.7
13
+ * @since 0.0.1
14
+ * @author AMBROISE PARK Consulting
15
+ */
16
+ export declare function EntityFormRenderer<T extends EntityRecord = EntityRecord>({ entity, onSubmit, t, className, submitText, loading, defaultValues, submitVariant, secondaryButtonText, secondaryButtonVariant, onSecondarySubmit, viewerRole, operation, autoSave, formId: externalFormId, cancelText, cancelPath, successPath, onCancel, warnOnUnsavedChanges, unsavedChangesMessage, hideVisibilityInfo, }: EntityFormRendererProps<T>): import("react/jsx-runtime").JSX.Element;
17
+ export default EntityFormRenderer;
18
+ //# sourceMappingURL=EntityFormRenderer.d.ts.map
@@ -0,0 +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+b5B;AAED,eAAe,kBAAkB,CAAC"}