@lad-sapienza/scms-core 0.1.0-alpha.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 (64) hide show
  1. package/LICENSE +14 -0
  2. package/README.md +73 -0
  3. package/components/BSNavbar.tsx +264 -0
  4. package/components/DataTb/DataTb.astro +33 -0
  5. package/components/DataTb/DataTb.tsx +340 -0
  6. package/components/DataTb/DataTbMdx.tsx +75 -0
  7. package/components/DataTb/README.md +275 -0
  8. package/components/DataTb/index.ts +36 -0
  9. package/components/DataTb/sources/ApiSource.tsx +89 -0
  10. package/components/DataTb/sources/CsvSource.tsx +69 -0
  11. package/components/DataTb/sources/DirectusSource.tsx +98 -0
  12. package/components/DataTb/sources/JsonSource.tsx +65 -0
  13. package/components/DataTb/sources/index.ts +17 -0
  14. package/components/DataTb/types.ts +215 -0
  15. package/components/DataTb/utils.ts +186 -0
  16. package/components/Gallery/Gallery.astro +65 -0
  17. package/components/Gallery/Gallery.tsx +136 -0
  18. package/components/Gallery/README.md +118 -0
  19. package/components/Gallery/__tests__/galleryUtils.test.ts +178 -0
  20. package/components/Gallery/galleryUtils.ts +186 -0
  21. package/components/Gallery/index.ts +7 -0
  22. package/components/Gallery/react-photoswipe-gallery.d.ts +26 -0
  23. package/components/Gallery/types.ts +46 -0
  24. package/components/Gallery/virtual-gallery.d.ts +15 -0
  25. package/components/Map/LayerControl.tsx +368 -0
  26. package/components/Map/Map.tsx +735 -0
  27. package/components/Map/MapMdx.tsx +37 -0
  28. package/components/Map/RasterLayerLibre.tsx +33 -0
  29. package/components/Map/Search/SearchOperators.ts +31 -0
  30. package/components/Map/Search/SearchUI.tsx +75 -0
  31. package/components/Map/Search/SearchUIAdvanced.tsx +289 -0
  32. package/components/Map/Search/SearchUISimple.tsx +115 -0
  33. package/components/Map/Search/index.ts +7 -0
  34. package/components/Map/defaultBasemaps.ts +88 -0
  35. package/components/Map/index.ts +6 -0
  36. package/components/Map/types.ts +179 -0
  37. package/components/Map/utils.ts +367 -0
  38. package/components/README.md +62 -0
  39. package/components/Record/Field.tsx +35 -0
  40. package/components/Record/Image.tsx +114 -0
  41. package/components/Record/RecordFetcher.tsx +127 -0
  42. package/components/Record/context.tsx +14 -0
  43. package/components/Record/index.ts +11 -0
  44. package/components/Record/useRecordFetcher.ts +104 -0
  45. package/components/Record/utils.ts +13 -0
  46. package/components/SEO/SEO.astro +132 -0
  47. package/components/TableOfContents/TableOfContents.astro +86 -0
  48. package/components/ZoteroGeoViewer/README.md +195 -0
  49. package/components/ZoteroGeoViewer/ZoteroGeoViewer.astro +26 -0
  50. package/components/ZoteroGeoViewer/ZoteroGeoViewer.tsx +483 -0
  51. package/components/ZoteroGeoViewer/ZoteroRecordsPreview.tsx +185 -0
  52. package/components/ZoteroGeoViewer/index.ts +16 -0
  53. package/components/ZoteroGeoViewer/types.ts +123 -0
  54. package/index.ts +26 -0
  55. package/integrations/__tests__/galleryIntegration.test.ts +70 -0
  56. package/integrations/contentAssetsIntegration.ts +132 -0
  57. package/integrations/directusLoader.ts +98 -0
  58. package/integrations/galleryIntegration.ts +78 -0
  59. package/package.json +56 -0
  60. package/scms.ts +95 -0
  61. package/utils/content.ts +88 -0
  62. package/utils/data-fetcher.ts +128 -0
  63. package/utils/directus-config.ts +160 -0
  64. package/utils/record-fetcher.ts +132 -0
@@ -0,0 +1,36 @@
1
+ /**
2
+ * DataTb Component Exports
3
+ *
4
+ * Exports the MDX-safe wrapper by default for better compatibility
5
+ * with Astro MDX files
6
+ */
7
+
8
+ export { DataTb } from './DataTbMdx';
9
+
10
+ // Source components
11
+ export {
12
+ CsvSource,
13
+ JsonSource,
14
+ DirectusSource,
15
+ ApiSource,
16
+ } from './sources';
17
+
18
+ // Types
19
+ export type {
20
+ DataRow,
21
+ ColumnConfig,
22
+ PaginationConfig,
23
+ DataTbProps,
24
+ CsvSourceConfig,
25
+ JsonSourceConfig,
26
+ DirectusSourceConfig,
27
+ ApiSourceConfig,
28
+ SourceConfig,
29
+ } from './types';
30
+
31
+ export type {
32
+ CsvSourceProps,
33
+ JsonSourceProps,
34
+ DirectusSourceProps,
35
+ ApiSourceProps,
36
+ } from './sources';
@@ -0,0 +1,89 @@
1
+ /**
2
+ * ApiSource Component
3
+ *
4
+ * Loads data from any API endpoint with custom transformation
5
+ */
6
+
7
+ import { useEffect } from 'react';
8
+ import type { DataRow, SourceComponentProps } from '../types';
9
+
10
+ export interface ApiSourceProps extends SourceComponentProps {
11
+ /** API endpoint URL */
12
+ url: string;
13
+
14
+ /** HTTP method (default: GET) */
15
+ method?: 'GET' | 'POST';
16
+
17
+ /** Request headers */
18
+ headers?: Record<string, string>;
19
+
20
+ /** Request body (for POST) */
21
+ body?: any;
22
+
23
+ /** Transform response to data rows */
24
+ transformer?: (data: any) => DataRow[];
25
+ }
26
+
27
+ export function ApiSource({
28
+ url,
29
+ method = 'GET',
30
+ headers,
31
+ body,
32
+ transformer,
33
+ onDataLoad,
34
+ onLoadingChange,
35
+ onError,
36
+ }: ApiSourceProps) {
37
+ useEffect(() => {
38
+ const loadApiData = async () => {
39
+ try {
40
+ onLoadingChange?.(true);
41
+
42
+ const options: RequestInit = {
43
+ method,
44
+ headers: {
45
+ 'Content-Type': 'application/json',
46
+ ...headers,
47
+ },
48
+ };
49
+
50
+ if (body && method === 'POST') {
51
+ options.body = JSON.stringify(body);
52
+ }
53
+
54
+ const response = await fetch(url, options);
55
+
56
+ if (!response.ok) {
57
+ throw new Error(`HTTP error! status: ${response.status}`);
58
+ }
59
+
60
+ const responseData = await response.json();
61
+
62
+ // Apply transformer if provided
63
+ let data: DataRow[];
64
+ if (transformer) {
65
+ data = transformer(responseData);
66
+ } else if (Array.isArray(responseData)) {
67
+ data = responseData;
68
+ } else if (responseData.data && Array.isArray(responseData.data)) {
69
+ // Common API pattern: { data: [...] }
70
+ data = responseData.data;
71
+ } else {
72
+ // Single object response
73
+ data = [responseData];
74
+ }
75
+
76
+ onDataLoad(data);
77
+ onLoadingChange?.(false);
78
+ } catch (error) {
79
+ onError?.(error instanceof Error ? error : new Error('Failed to load API data'));
80
+ onLoadingChange?.(false);
81
+ }
82
+ };
83
+
84
+ loadApiData();
85
+ }, [url, method, JSON.stringify(headers), JSON.stringify(body)]);
86
+
87
+ // This component doesn't render anything
88
+ return null;
89
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * CsvSource Component
3
+ *
4
+ * Loads data from CSV files (local or remote)
5
+ * Uses PapaParse for parsing
6
+ */
7
+
8
+ import { useEffect } from 'react';
9
+ import Papa from 'papaparse';
10
+ import type { SourceComponentProps } from '../types';
11
+
12
+ export interface CsvSourceProps extends SourceComponentProps {
13
+ /** CSV file URL (local or remote) */
14
+ url: string;
15
+
16
+ /** Delimiter character (default: ',') */
17
+ delimiter?: string;
18
+
19
+ /** Skip first N rows */
20
+ skipRows?: number;
21
+ }
22
+
23
+ export function CsvSource({
24
+ url,
25
+ delimiter = ',',
26
+ skipRows = 0,
27
+ onDataLoad,
28
+ onLoadingChange,
29
+ onError,
30
+ }: CsvSourceProps) {
31
+ useEffect(() => {
32
+ const loadCsv = async () => {
33
+ try {
34
+ onLoadingChange?.(true);
35
+
36
+ Papa.parse(url, {
37
+ download: true,
38
+ header: true,
39
+ delimiter,
40
+ skipEmptyLines: true,
41
+ dynamicTyping: true, // Convert numbers and booleans
42
+ complete: (results) => {
43
+ let data = results.data;
44
+
45
+ // Skip rows if specified
46
+ if (skipRows > 0) {
47
+ data = data.slice(skipRows);
48
+ }
49
+
50
+ onDataLoad(data as import('../types').DataRow[]);
51
+ onLoadingChange?.(false);
52
+ },
53
+ error: (error) => {
54
+ onError?.(new Error(`Failed to parse CSV: ${error.message}`));
55
+ onLoadingChange?.(false);
56
+ },
57
+ });
58
+ } catch (error) {
59
+ onError?.(error instanceof Error ? error : new Error('Failed to load CSV'));
60
+ onLoadingChange?.(false);
61
+ }
62
+ };
63
+
64
+ loadCsv();
65
+ }, [url, delimiter, skipRows]);
66
+
67
+ // This component doesn't render anything
68
+ return null;
69
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * DirectusSource Component
3
+ *
4
+ * Loads data from Directus collections using the Directus SDK
5
+ * Requires directusConfig in user.config.mjs
6
+ */
7
+
8
+ import { useEffect } from 'react';
9
+ import { createDirectus, rest, staticToken, readItems } from '@directus/sdk';
10
+ import type { SourceComponentProps } from '../types';
11
+
12
+ export interface DirectusSourceProps extends SourceComponentProps {
13
+ /** Collection name */
14
+ collection: string;
15
+
16
+ /** Directus configuration */
17
+ config: {
18
+ url: string;
19
+ token: string;
20
+ };
21
+
22
+ /** Filter query */
23
+ filter?: Record<string, any>;
24
+
25
+ /** Fields to select */
26
+ fields?: string[];
27
+
28
+ /** Sort order (e.g., ['-date_created', 'title']) */
29
+ sort?: string[];
30
+
31
+ /** Limit results */
32
+ limit?: number;
33
+ }
34
+
35
+ export function DirectusSource({
36
+ collection,
37
+ config,
38
+ filter,
39
+ fields,
40
+ sort,
41
+ limit,
42
+ onDataLoad,
43
+ onLoadingChange,
44
+ onError,
45
+ }: DirectusSourceProps) {
46
+ useEffect(() => {
47
+ const loadDirectusData = async () => {
48
+ try {
49
+ if (!config.url || !config.token) {
50
+ throw new Error('Directus configuration (url and token) is required');
51
+ }
52
+
53
+ onLoadingChange?.(true);
54
+
55
+ // Create Directus client
56
+ const client = createDirectus(config.url)
57
+ .with(staticToken(config.token))
58
+ .with(rest());
59
+
60
+ // Build query options
61
+ const queryOptions: any = {};
62
+
63
+ if (filter) {
64
+ queryOptions.filter = filter;
65
+ }
66
+
67
+ if (fields) {
68
+ queryOptions.fields = fields;
69
+ }
70
+
71
+ if (sort) {
72
+ queryOptions.sort = sort;
73
+ }
74
+
75
+ if (limit) {
76
+ queryOptions.limit = limit;
77
+ }
78
+
79
+ // Fetch data
80
+ const items = await client.request(
81
+ readItems(collection, queryOptions)
82
+ );
83
+
84
+ onDataLoad(items as any[]);
85
+ onLoadingChange?.(false);
86
+ } catch (error) {
87
+ const message = error instanceof Error ? error.message : 'Failed to load Directus data';
88
+ onError?.(new Error(`Directus error: ${message}`));
89
+ onLoadingChange?.(false);
90
+ }
91
+ };
92
+
93
+ loadDirectusData();
94
+ }, [collection, config.url, config.token, JSON.stringify(filter), JSON.stringify(fields), JSON.stringify(sort), limit]);
95
+
96
+ // This component doesn't render anything
97
+ return null;
98
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * JsonSource Component
3
+ *
4
+ * Loads data from JSON (inline or remote)
5
+ */
6
+
7
+ import { useEffect } from 'react';
8
+ import type { DataRow, SourceComponentProps } from '../types';
9
+
10
+ export interface JsonSourceProps extends SourceComponentProps {
11
+ /** Inline JSON data */
12
+ data?: DataRow[];
13
+
14
+ /** URL to fetch JSON from */
15
+ url?: string;
16
+ }
17
+
18
+ export function JsonSource({
19
+ data,
20
+ url,
21
+ onDataLoad,
22
+ onLoadingChange,
23
+ onError,
24
+ }: JsonSourceProps) {
25
+ useEffect(() => {
26
+ const loadJson = async () => {
27
+ try {
28
+ // Use inline data if provided
29
+ if (data) {
30
+ onDataLoad(data);
31
+ return;
32
+ }
33
+
34
+ // Fetch from URL
35
+ if (!url) {
36
+ onError?.(new Error('JsonSource requires either data or url prop'));
37
+ return;
38
+ }
39
+
40
+ onLoadingChange?.(true);
41
+
42
+ const response = await fetch(url);
43
+ if (!response.ok) {
44
+ throw new Error(`HTTP error! status: ${response.status}`);
45
+ }
46
+
47
+ const jsonData = await response.json();
48
+
49
+ // Handle both array and object responses
50
+ const arrayData = Array.isArray(jsonData) ? jsonData : [jsonData];
51
+
52
+ onDataLoad(arrayData);
53
+ onLoadingChange?.(false);
54
+ } catch (error) {
55
+ onError?.(error instanceof Error ? error : new Error('Failed to load JSON'));
56
+ onLoadingChange?.(false);
57
+ }
58
+ };
59
+
60
+ loadJson();
61
+ }, [data, url]);
62
+
63
+ // This component doesn't render anything
64
+ return null;
65
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * DataTb Source Components
3
+ *
4
+ * Export all source components for easy importing
5
+ */
6
+
7
+ export { CsvSource } from './CsvSource';
8
+ export type { CsvSourceProps } from './CsvSource';
9
+
10
+ export { JsonSource } from './JsonSource';
11
+ export type { JsonSourceProps } from './JsonSource';
12
+
13
+ export { DirectusSource } from './DirectusSource';
14
+ export type { DirectusSourceProps } from './DirectusSource';
15
+
16
+ export { ApiSource } from './ApiSource';
17
+ export type { ApiSourceProps } from './ApiSource';
@@ -0,0 +1,215 @@
1
+ /**
2
+ * DataTb Type Definitions
3
+ *
4
+ * Core types for the DataTb component system
5
+ */
6
+
7
+
8
+ import type { DirectusShorthand, DirectusSourceConfig } from '../../utils/directus-config';
9
+
10
+ declare module '@tanstack/react-table' {
11
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
12
+ interface ColumnMeta<TData = unknown, TValue = unknown> {
13
+ /** Raw CSS width from ColumnConfig.width, applied directly to the <th>/<td> (any CSS length unit). */
14
+ width?: string;
15
+ }
16
+ }
17
+
18
+ /**
19
+ * Base data row type - can be extended with specific fields
20
+ */
21
+ export type DataRow = Record<string, any>;
22
+
23
+ /**
24
+ * Column configuration with optional formatting
25
+ */
26
+ export interface ColumnConfig {
27
+ /** Column key matching data property */
28
+ key: string;
29
+
30
+ /** Display header text */
31
+ header: string;
32
+
33
+ /** Enable sorting for this column */
34
+ sortable?: boolean;
35
+
36
+ /** Format type for automatic formatting */
37
+ format?: 'date' | 'number' | 'currency' | 'percent';
38
+
39
+ /** Custom render function */
40
+ render?: (value: any, row: DataRow) => React.ReactNode;
41
+
42
+ /** Value mapping labels (e.g. { "true": "Yes", "false": "No" }) */
43
+ labels?: Record<string, string>;
44
+
45
+ /** Column width (CSS value) */
46
+ width?: string;
47
+ }
48
+
49
+ /**
50
+ * Pagination configuration
51
+ */
52
+ export interface PaginationConfig {
53
+ /** Number of rows per page */
54
+ pageSize?: number;
55
+
56
+ /** Show page size selector */
57
+ showPageSize?: boolean;
58
+
59
+ /** Available page size options */
60
+ pageSizeOptions?: number[];
61
+ }
62
+
63
+ /**
64
+ * Source type discriminators
65
+ */
66
+ export type SourceType = 'csv' | 'json' | 'directus' | 'api';
67
+
68
+ /**
69
+ * CSV Source configuration
70
+ */
71
+ export interface CsvSourceConfig {
72
+ type: 'csv';
73
+ url: string;
74
+ /** Delimiter (default: ',') */
75
+ delimiter?: string;
76
+ /** Skip first N rows */
77
+ skipRows?: number;
78
+ /** Longitude column name (for map conversion) */
79
+ lng?: string;
80
+ /** Latitude column name (for map conversion) */
81
+ lat?: string;
82
+ }
83
+
84
+ /**
85
+ * JSON Source configuration
86
+ */
87
+ export interface JsonSourceConfig {
88
+ type: 'json';
89
+ /** Inline data */
90
+ data?: DataRow[];
91
+ /** URL to fetch JSON from */
92
+ url?: string;
93
+ }
94
+
95
+ // Re-export DirectusSourceConfig from shared utils
96
+ export type { DirectusSourceConfig } from '../../utils/directus-config';
97
+
98
+ /**
99
+ * Generic API Source configuration
100
+ */
101
+ export interface ApiSourceConfig {
102
+ type: 'api';
103
+ /** API endpoint URL */
104
+ url: string;
105
+ /** HTTP method */
106
+ method?: 'GET' | 'POST';
107
+ /** Request headers */
108
+ headers?: Record<string, string>;
109
+ /** Request body (for POST) */
110
+ body?: any;
111
+ /** Transform response data */
112
+ transformer?: (data: any) => DataRow[];
113
+ }
114
+
115
+ /**
116
+ * GeoJSON Source configuration
117
+ */
118
+ export interface GeoJsonSourceConfig {
119
+ type: 'geojson';
120
+ /** GeoJSON data object */
121
+ data?: any;
122
+ /** URL to fetch GeoJSON from */
123
+ url?: string;
124
+ }
125
+
126
+ /**
127
+ * Vector Tile Source configuration (from MapLibre style JSON)
128
+ */
129
+ export interface VectorSourceConfig {
130
+ type: 'vector';
131
+ /** Vector tile URL or template */
132
+ url?: string;
133
+ }
134
+
135
+ /**
136
+ * Union of all source configurations
137
+ */
138
+ export type SourceConfig =
139
+ | CsvSourceConfig
140
+ | JsonSourceConfig
141
+ | DirectusSourceConfig
142
+ | ApiSourceConfig
143
+ | GeoJsonSourceConfig
144
+ | VectorSourceConfig;
145
+
146
+ /**
147
+ * Props for source components
148
+ */
149
+ export interface SourceComponentProps {
150
+ /** Callback to provide data to parent */
151
+ onDataLoad: (data: DataRow[]) => void;
152
+ /** Callback for loading state */
153
+ onLoadingChange?: (loading: boolean) => void;
154
+ /** Callback for error state */
155
+ onError?: (error: Error) => void;
156
+ }
157
+
158
+ /**
159
+ * DataTb main component props
160
+ */
161
+ export interface DataTbProps {
162
+ /** Column definitions (optional - will auto-detect if not provided) */
163
+ columns?: ColumnConfig[];
164
+
165
+ /** Enable global search/filter */
166
+ searchable?: boolean;
167
+
168
+ /** Pagination configuration */
169
+ pagination?: boolean | PaginationConfig;
170
+
171
+ /** Enable column sorting */
172
+ sortable?: boolean;
173
+
174
+ /** Initial sort state */
175
+ initialSort?: {
176
+ columnKey: string;
177
+ direction: 'asc' | 'desc';
178
+ };
179
+
180
+ /** Custom CSS classes */
181
+ className?: string;
182
+
183
+ /** Data source configuration */
184
+ source?: SourceConfig;
185
+
186
+ /** Loading state message */
187
+ loadingMessage?: string;
188
+
189
+ /** Empty state message */
190
+ emptyMessage?: string;
191
+
192
+ /** Error message */
193
+ errorMessage?: string;
194
+
195
+ /** Truncate large cell content by default */
196
+ truncateContent?: boolean;
197
+
198
+ /** Max width for truncated cell content (CSS value) */
199
+ truncateMaxWidth?: string;
200
+
201
+ /** Table body content size */
202
+ contentSize?: 'sm' | 'md';
203
+
204
+ /** Simplified CSV Source URL (MDX friendly) */
205
+ csv?: string;
206
+
207
+ /** Simplified JSON Source Data or URL (MDX friendly) */
208
+ json?: any[] | string;
209
+
210
+ /** Simplified API Source URL (MDX friendly) */
211
+ api?: string;
212
+
213
+ /** Simplified Directus configuration (MDX friendly) - uses same interface as Map component */
214
+ directus?: DirectusShorthand;
215
+ }