@pattern-stack/frontend-patterns 0.2.0-alpha.0 → 0.2.0-alpha.3

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 (56) hide show
  1. package/cli/commands/generate-hooks.ts +13 -4
  2. package/cli/src/codegen/openapi/__tests__/naming-utils.test.js +367 -0
  3. package/cli/src/codegen/openapi/client-generator.js +87 -19
  4. package/cli/src/codegen/openapi/confidence-scorer.js +93 -0
  5. package/cli/src/codegen/openapi/hook-config.js +48 -0
  6. package/cli/src/codegen/openapi/hook-generator.js +100 -62
  7. package/cli/src/codegen/openapi/naming-constants.js +98 -0
  8. package/cli/src/codegen/openapi/naming-utils.js +149 -0
  9. package/dist/atoms/components/core/Badge/Badge.d.ts +1 -1
  10. package/dist/atoms/components/data/DataTable/DataTable.d.ts.map +1 -1
  11. package/dist/atoms/components/data/DataTable/DataTable.types.d.ts +15 -3
  12. package/dist/atoms/components/data/DataTable/DataTable.types.d.ts.map +1 -1
  13. package/dist/atoms/hooks/index.d.ts +2 -0
  14. package/dist/atoms/hooks/index.d.ts.map +1 -1
  15. package/dist/atoms/hooks/useFieldMetadata.d.ts +18 -0
  16. package/dist/atoms/hooks/useFieldMetadata.d.ts.map +1 -0
  17. package/dist/atoms/hooks/useResponsiveTable.d.ts +103 -0
  18. package/dist/atoms/hooks/useResponsiveTable.d.ts.map +1 -0
  19. package/dist/atoms/primitives/sheet.d.ts +23 -0
  20. package/dist/atoms/primitives/sheet.d.ts.map +1 -0
  21. package/dist/atoms/services/auth-service.d.ts.map +1 -1
  22. package/dist/atoms/types/auth.d.ts +51 -0
  23. package/dist/atoms/types/auth.d.ts.map +1 -1
  24. package/dist/atoms/types/index.d.ts +1 -0
  25. package/dist/atoms/types/index.d.ts.map +1 -1
  26. package/dist/atoms/types/ui-config.d.ts +25 -8
  27. package/dist/atoms/types/ui-config.d.ts.map +1 -1
  28. package/dist/atoms/types/ui-metadata.d.ts +112 -0
  29. package/dist/atoms/types/ui-metadata.d.ts.map +1 -0
  30. package/dist/atoms/utils/ui-mapping.d.ts +9 -3
  31. package/dist/atoms/utils/ui-mapping.d.ts.map +1 -1
  32. package/dist/features/auth/hooks/useAuth.d.ts.map +1 -1
  33. package/dist/frontend-patterns.css +82 -0
  34. package/dist/index.d.ts +4 -1
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.es.js +1030 -248
  37. package/dist/index.es.js.map +1 -1
  38. package/dist/index.js +1031 -248
  39. package/dist/index.js.map +1 -1
  40. package/dist/molecules/layout/ListToolbar/ListToolbar.d.ts +37 -0
  41. package/dist/molecules/layout/ListToolbar/ListToolbar.d.ts.map +1 -0
  42. package/dist/molecules/layout/ListToolbar/index.d.ts +2 -0
  43. package/dist/molecules/layout/ListToolbar/index.d.ts.map +1 -0
  44. package/dist/molecules/layout/PageTitle/PageTitle.d.ts +17 -0
  45. package/dist/molecules/layout/PageTitle/PageTitle.d.ts.map +1 -0
  46. package/dist/molecules/layout/PageTitle/index.d.ts +2 -0
  47. package/dist/molecules/layout/PageTitle/index.d.ts.map +1 -0
  48. package/dist/molecules/layout/index.d.ts +2 -0
  49. package/dist/molecules/layout/index.d.ts.map +1 -1
  50. package/dist/templates/ListPageTemplate.d.ts +21 -0
  51. package/dist/templates/ListPageTemplate.d.ts.map +1 -0
  52. package/dist/templates/admin/AdminCRUDTemplate.d.ts.map +1 -1
  53. package/dist/templates/factory.d.ts.map +1 -1
  54. package/dist/templates/index.d.ts +1 -0
  55. package/dist/templates/index.d.ts.map +1 -1
  56. package/package.json +4 -3
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Hook Configuration Manager (Stub)
3
+ *
4
+ * Provides name overrides and pattern configuration for hook generation.
5
+ * This is a minimal stub to unblock the hook generator.
6
+ */
7
+
8
+ export class HookConfigManager {
9
+ constructor(configPath) {
10
+ this.configPath = configPath;
11
+ this.overrides = {};
12
+ this.patterns = {
13
+ deduplicateSegments: true
14
+ };
15
+ this.reviewedNames = [];
16
+ this.statistics = {};
17
+ }
18
+
19
+ async load() {
20
+ // No-op for stub - would load from config file
21
+ return;
22
+ }
23
+
24
+ async save() {
25
+ // No-op for stub - would save to config file
26
+ return;
27
+ }
28
+
29
+ getOverride(operationId) {
30
+ return this.overrides[operationId] || null;
31
+ }
32
+
33
+ getPatterns() {
34
+ return this.patterns;
35
+ }
36
+
37
+ getReviewedNames() {
38
+ return this.reviewedNames;
39
+ }
40
+
41
+ updateStatistics(stats) {
42
+ this.statistics = { ...this.statistics, ...stats };
43
+ }
44
+
45
+ setOverride(operationId, hookName) {
46
+ this.overrides[operationId] = hookName;
47
+ }
48
+ }
@@ -6,8 +6,10 @@
6
6
  *
7
7
  * Part of FRO-3: React Hook Generator
8
8
  */
9
- import { HookConfigManager } from './hook-config';
10
- import { ConfidenceScorer } from './confidence-scorer';
9
+ import { HookConfigManager } from './hook-config.js';
10
+ import { ConfidenceScorer } from './confidence-scorer.js';
11
+ import { singularize, pluralize, isPlural } from './naming-utils.js';
12
+ import { AUTH_ACTIONS, HEALTH_ENDPOINTS, USER_PROFILE_ENDPOINTS, SINGLETON_RESOURCES, COLLECTION_ACTIONS, SPECIAL_ACTIONS } from './naming-constants.js';
11
13
  export class ReactHookGenerator {
12
14
  options;
13
15
  configManager;
@@ -203,13 +205,13 @@ export class ReactHookGenerator {
203
205
  return ` onMutate: async (newData) => {
204
206
  // Cancel outgoing refetches
205
207
  await queryClient.cancelQueries({ queryKey: queryKeys.all })
206
-
208
+
207
209
  // Snapshot previous value
208
210
  const previousData = queryClient.getQueryData(queryKeys.all)
209
-
211
+
210
212
  // Optimistically update cache
211
213
  queryClient.setQueryData(queryKeys.all, (old: any) => [...(old || []), newData])
212
-
214
+
213
215
  return { previousData }
214
216
  },
215
217
  onError: (err, newData, context) => {
@@ -220,14 +222,14 @@ export class ReactHookGenerator {
220
222
  case 'patch':
221
223
  return ` onMutate: async (updatedData) => {
222
224
  await queryClient.cancelQueries({ queryKey: queryKeys.all })
223
-
225
+
224
226
  const previousData = queryClient.getQueryData(queryKeys.all)
225
-
227
+
226
228
  // Update specific item in cache
227
- queryClient.setQueryData(queryKeys.all, (old: any) =>
229
+ queryClient.setQueryData(queryKeys.all, (old: any) =>
228
230
  old?.map((item: any) => item.id === updatedData.id ? { ...item, ...updatedData } : item)
229
231
  )
230
-
232
+
231
233
  return { previousData }
232
234
  },
233
235
  onError: (err, updatedData, context) => {
@@ -236,14 +238,14 @@ export class ReactHookGenerator {
236
238
  case 'delete':
237
239
  return ` onMutate: async (id) => {
238
240
  await queryClient.cancelQueries({ queryKey: queryKeys.all })
239
-
241
+
240
242
  const previousData = queryClient.getQueryData(queryKeys.all)
241
-
243
+
242
244
  // Remove item from cache
243
- queryClient.setQueryData(queryKeys.all, (old: any) =>
245
+ queryClient.setQueryData(queryKeys.all, (old: any) =>
244
246
  old?.filter((item: any) => item.id !== id)
245
247
  )
246
-
248
+
247
249
  return { previousData }
248
250
  },
249
251
  onError: (err, id, context) => {
@@ -275,18 +277,36 @@ export class ReactHookGenerator {
275
277
  // Group endpoints by resource/tag
276
278
  const groupedEndpoints = this.groupEndpointsByResource(endpoints);
277
279
  for (const [resource, resourceEndpoints] of Object.entries(groupedEndpoints)) {
280
+ // Sanitize resource name to be a valid JS identifier
281
+ const sanitizedResource = this.sanitizeIdentifier(resource);
282
+ console.log(`DEBUG: resource="${resource}" -> sanitizedResource="${sanitizedResource}"`);
278
283
  keys.push(` // ${resource} keys`);
279
- keys.push(` ${resource}: () => [...queryKeys.all, '${resource}'] as const,`);
284
+ keys.push(` ${sanitizedResource}: () => [...queryKeys.all, '${sanitizedResource}'] as const,`);
285
+ const usedKeys = new Set([sanitizedResource]); // Track used keys to avoid duplicates
280
286
  for (const endpoint of resourceEndpoints) {
281
287
  if (endpoint.method !== 'get')
282
288
  continue;
283
289
  const operationName = this.getOperationName(endpoint);
284
- const keyName = this.camelCase(operationName.replace(/^get/, ''));
290
+ let keyName = this.camelCase(operationName.replace(/^get/, ''));
291
+ // Skip if keyName matches sanitizedResource (base key already handles this)
292
+ if (keyName === sanitizedResource) {
293
+ continue;
294
+ }
295
+ // If keyName already used, add suffix to disambiguate
296
+ if (usedKeys.has(keyName)) {
297
+ const isList = !endpoint.path.includes('{');
298
+ keyName = isList ? `${keyName}List` : `${keyName}Detail`;
299
+ }
300
+ // Still duplicate after suffix? Skip to avoid compilation error
301
+ if (usedKeys.has(keyName)) {
302
+ continue;
303
+ }
304
+ usedKeys.add(keyName);
285
305
  if (this.hasRequiredParams(endpoint)) {
286
- keys.push(` ${keyName}: (params: any) => [...queryKeys.${resource}(), '${keyName}', params] as const,`);
306
+ keys.push(` ${keyName}: (params: Record<string, unknown>) => [...queryKeys.${sanitizedResource}(), '${keyName}', params] as const,`);
287
307
  }
288
308
  else {
289
- keys.push(` ${keyName}: () => [...queryKeys.${resource}(), '${keyName}'] as const,`);
309
+ keys.push(` ${keyName}: () => [...queryKeys.${sanitizedResource}(), '${keyName}'] as const,`);
290
310
  }
291
311
  }
292
312
  keys.push('');
@@ -336,7 +356,7 @@ export class ReactHookGenerator {
336
356
  generateFileHeader(title) {
337
357
  return `/**
338
358
  * ${title}
339
- *
359
+ *
340
360
  * Auto-generated React hooks from OpenAPI specification
341
361
  * Do not edit manually - regenerate using the hook generator
342
362
  */`;
@@ -422,6 +442,8 @@ export class ReactHookGenerator {
422
442
  /^(?<action>\w+?)_api_v\d+_(?<path>.+?)_(?<method>get|post|put|patch|delete)$/i,
423
443
  // Pattern: action_resource_path_method
424
444
  /^(?<action>\w+?)_(?<resource>\w+?)_(?<path>.+?)_(?<method>get|post|put|patch|delete)$/i,
445
+ // Pattern: simple action_action_method (e.g., ready_ready_get, health_health_get)
446
+ /^(?<action>\w+?)_\1_(?<method>get|post|put|patch|delete)$/i,
425
447
  // Pattern: simple action_resource
426
448
  /^(?<action>\w+?)_(?<resource>\w+?)$/i,
427
449
  ];
@@ -439,7 +461,12 @@ export class ReactHookGenerator {
439
461
  // Check if it's a custom action
440
462
  const urlSegments = endpoint.path.split('/');
441
463
  const lastSegment = urlSegments[urlSegments.length - 1];
442
- const isCustomAction = !lastSegment.startsWith('{') && urlSegments[urlSegments.length - 2]?.startsWith('{');
464
+ const prevSegment = urlSegments[urlSegments.length - 2];
465
+ // Custom action: either after a path param (e.g., /accounts/{id}/archive)
466
+ // or a known action verb on a collection (e.g., /activities/search)
467
+ const isResourceAction = !lastSegment.startsWith('{') && prevSegment?.startsWith('{');
468
+ const isCollectionAction = !lastSegment.startsWith('{') && !prevSegment?.startsWith('{') && COLLECTION_ACTIONS.includes(lastSegment);
469
+ const isCustomAction = isResourceAction || isCollectionAction;
443
470
  // Extract or infer the action verb
444
471
  const actionVerb = this.extractActionVerb(components.action || operationId, endpoint.method);
445
472
  // Extract or infer the resource
@@ -449,7 +476,7 @@ export class ReactHookGenerator {
449
476
  'resource';
450
477
  return {
451
478
  action: actionVerb,
452
- resource: resource,
479
+ resource: isCollectionAction ? prevSegment : resource,
453
480
  path: components.path || '',
454
481
  method: endpoint.method,
455
482
  isCustomAction,
@@ -463,10 +490,10 @@ export class ReactHookGenerator {
463
490
  extractActionVerb(actionPart, method) {
464
491
  // Common action verbs to recognize
465
492
  const actionVerbs = [
466
- 'login', 'logout', 'register', 'signup', 'signin',
467
- 'health', 'status', 'check',
493
+ ...AUTH_ACTIONS,
494
+ ...HEALTH_ENDPOINTS,
468
495
  'list', 'get', 'create', 'update', 'delete',
469
- 'reassign', 'assign', 'settle', 'reconcile',
496
+ ...COLLECTION_ACTIONS,
470
497
  'approve', 'reject', 'archive', 'restore',
471
498
  'sync', 'refresh', 'reset', 'verify', 'validate'
472
499
  ];
@@ -501,11 +528,7 @@ export class ReactHookGenerator {
501
528
  * Check if this is a special endpoint that should keep its action verb
502
529
  */
503
530
  isSpecialEndpoint(parsed) {
504
- const specialActions = [
505
- 'login', 'logout', 'register', 'signup', 'signin',
506
- 'health', 'status', 'check', 'verify', 'validate'
507
- ];
508
- return specialActions.includes(parsed.action);
531
+ return SPECIAL_ACTIONS.includes(parsed.action);
509
532
  }
510
533
  /**
511
534
  * Format name for custom action endpoints
@@ -519,17 +542,18 @@ export class ReactHookGenerator {
519
542
  return customActionName;
520
543
  }
521
544
  // Otherwise combine resource and action
522
- return `${this.singularize(resource)}_${customActionName}`;
545
+ return `${singularize(resource)}_${customActionName}`;
523
546
  }
524
- return `${action}_${this.singularize(resource)}`;
547
+ return `${action}_${singularize(resource)}`;
525
548
  }
526
549
  /**
527
550
  * Format name for special endpoints (login, health, etc.)
528
551
  */
529
552
  formatSpecialEndpointName(parsed) {
530
553
  const { action, resource } = parsed;
531
- // For endpoints like "login", "health", just return the action
532
- if (['login', 'logout', 'health', 'status'].includes(action)) {
554
+ // For simple singleton endpoints, just return the action
555
+ const simpleActions = [...AUTH_ACTIONS.filter(a => ['login', 'logout'].includes(a)), ...HEALTH_ENDPOINTS.filter(h => ['health', 'ready', 'status'].includes(h)), ...USER_PROFILE_ENDPOINTS];
556
+ if (simpleActions.includes(action)) {
533
557
  return action;
534
558
  }
535
559
  // For other special endpoints, combine with resource if meaningful
@@ -544,19 +568,19 @@ export class ReactHookGenerator {
544
568
  formatStandardOperationName(parsed, endpoint) {
545
569
  const { resource } = parsed;
546
570
  // Determine if it's a list or single resource operation
547
- const isList = endpoint.method === 'get' && !endpoint.path.includes('{');
571
+ const isList = endpoint.method === 'get' && !endpoint.path.includes('{') && !this.isSingletonEndpoint(resource, endpoint.path);
548
572
  switch (endpoint.method) {
549
573
  case 'get':
550
- return isList ? this.pluralize(resource) : this.singularize(resource);
574
+ return isList ? pluralize(resource) : singularize(resource);
551
575
  case 'post':
552
- return `create_${this.singularize(resource)}`;
576
+ return `create_${singularize(resource)}`;
553
577
  case 'put':
554
578
  case 'patch':
555
- return `update_${this.singularize(resource)}`;
579
+ return `update_${singularize(resource)}`;
556
580
  case 'delete':
557
- return `delete_${this.singularize(resource)}`;
581
+ return `delete_${singularize(resource)}`;
558
582
  default:
559
- return this.singularize(resource);
583
+ return singularize(resource);
560
584
  }
561
585
  }
562
586
  generateCleanName(endpoint) {
@@ -578,7 +602,7 @@ export class ReactHookGenerator {
578
602
  const action = lastSegment.replace(/-/g, '_');
579
603
  return `${mainResource}_${action}`;
580
604
  }
581
- // For nested resources (e.g., /categories/{id}/subcategories),
605
+ // For nested resources (e.g., /categories/{id}/subcategories),
582
606
  // just use the last resource name
583
607
  if (pathParts.length > 2 && segments.some(s => s.startsWith('{'))) {
584
608
  // This is a nested resource, just use the last part
@@ -591,7 +615,7 @@ export class ReactHookGenerator {
591
615
  const isList = !endpoint.path.includes('{');
592
616
  // Only pluralize if it's a list and not already plural
593
617
  if (isList && !cleanResource.endsWith('s') && !cleanResource.endsWith('ies')) {
594
- return this.pluralize(cleanResource);
618
+ return pluralize(cleanResource);
595
619
  }
596
620
  return cleanResource;
597
621
  case 'post':
@@ -605,28 +629,22 @@ export class ReactHookGenerator {
605
629
  return `${endpoint.method}_${cleanResource}`;
606
630
  }
607
631
  }
608
- pluralize(word) {
609
- // Simple pluralization rules
610
- if (word.endsWith('y') && !['ay', 'ey', 'iy', 'oy', 'uy'].includes(word.slice(-2))) {
611
- return word.slice(0, -1) + 'ies';
632
+ isSingletonEndpoint(resource, path) {
633
+ // Check if resource matches singleton pattern
634
+ const lowerResource = resource.toLowerCase().replace(/_/g, '');
635
+ if (SINGLETON_RESOURCES.some(s => lowerResource === s || lowerResource.endsWith(s))) {
636
+ return true;
612
637
  }
613
- if (word.endsWith('s') || word.endsWith('x') || word.endsWith('ch') || word.endsWith('sh')) {
614
- return word + 'es';
638
+
639
+ // Check path patterns for common singletons
640
+ const pathSegments = path.split('/').filter(s => s && !s.startsWith('{'));
641
+ const lastSegment = pathSegments[pathSegments.length - 1]?.toLowerCase();
642
+
643
+ if (SINGLETON_RESOURCES.includes(lastSegment)) {
644
+ return true;
615
645
  }
616
- return word + 's';
617
- }
618
- singularize(word) {
619
- // Simple singularization rules
620
- if (word.endsWith('ies')) {
621
- return word.slice(0, -3) + 'y';
622
- }
623
- if (word.endsWith('ses') || word.endsWith('xes') || word.endsWith('ches') || word.endsWith('shes')) {
624
- return word.slice(0, -2);
625
- }
626
- if (word.endsWith('s') && !word.endsWith('ss')) {
627
- return word.slice(0, -1);
628
- }
629
- return word;
646
+
647
+ return false;
630
648
  }
631
649
  hasRequiredParams(endpoint) {
632
650
  return endpoint.parameters.some(p => p.required) ||
@@ -663,6 +681,15 @@ export class ReactHookGenerator {
663
681
  }
664
682
  }
665
683
  isListEndpoint(endpoint) {
684
+ // Extract resource for singleton check
685
+ const pathSegments = endpoint.path.split('/').filter(s => s && !s.startsWith('{'));
686
+ const resource = pathSegments[pathSegments.length - 1]?.replace(/-/g, '_') || '';
687
+
688
+ // Singleton endpoints are never list endpoints
689
+ if (this.isSingletonEndpoint(resource, endpoint.path)) {
690
+ return false;
691
+ }
692
+
666
693
  return endpoint.path.includes('list') ||
667
694
  !endpoint.path.includes('{') ||
668
695
  (endpoint.summary?.toLowerCase().includes('list') ?? false) ||
@@ -671,7 +698,9 @@ export class ReactHookGenerator {
671
698
  groupEndpointsByResource(endpoints) {
672
699
  const groups = {};
673
700
  for (const endpoint of endpoints) {
674
- const resource = endpoint.tags?.[0] || 'default';
701
+ const tag = endpoint.tags?.[0] || 'default';
702
+ // Sanitize tag to be a valid JS identifier (camelCase, no spaces)
703
+ const resource = this.sanitizeIdentifier(tag);
675
704
  if (!groups[resource]) {
676
705
  groups[resource] = [];
677
706
  }
@@ -679,8 +708,17 @@ export class ReactHookGenerator {
679
708
  }
680
709
  return groups;
681
710
  }
711
+ sanitizeIdentifier(str) {
712
+ // Convert to camelCase and remove invalid characters
713
+ return str
714
+ .replace(/[^a-zA-Z0-9\s]/g, '') // Remove special chars except spaces
715
+ .split(/\s+/) // Split on whitespace
716
+ .map((word, i) => i === 0 ? word.toLowerCase() : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
717
+ .join('');
718
+ }
682
719
  getRelatedQueryTags(endpoint) {
683
- const resource = endpoint.tags?.[0] || 'default';
720
+ const tag = endpoint.tags?.[0] || 'default';
721
+ const resource = this.sanitizeIdentifier(tag);
684
722
  return [resource, 'all'];
685
723
  }
686
724
  camelCase(str) {
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Naming Constants
3
+ *
4
+ * Shared constants for magic strings used across hook and client generators.
5
+ * These constants help identify special endpoints that get unique naming treatment.
6
+ */
7
+
8
+ /**
9
+ * Authentication action endpoints
10
+ * These endpoints are named directly without CRUD prefixes (e.g., 'login' not 'createLogin')
11
+ */
12
+ export const AUTH_ACTIONS = [
13
+ 'login',
14
+ 'logout',
15
+ 'register',
16
+ 'signup',
17
+ 'signin',
18
+ 'refresh'
19
+ ];
20
+
21
+ /**
22
+ * Health/status check endpoints
23
+ * These are singleton endpoints that return system status
24
+ */
25
+ export const HEALTH_ENDPOINTS = [
26
+ 'health',
27
+ 'ready',
28
+ 'status',
29
+ 'info',
30
+ 'version'
31
+ ];
32
+
33
+ /**
34
+ * User profile endpoints
35
+ * These endpoints reference the current/authenticated user
36
+ */
37
+ export const USER_PROFILE_ENDPOINTS = [
38
+ 'me',
39
+ 'self',
40
+ 'profile',
41
+ 'current'
42
+ ];
43
+
44
+ /**
45
+ * Singleton resources
46
+ * Resources that return a single item, not a collection
47
+ * Includes all auth, health, and user profile endpoints plus common singleton patterns
48
+ */
49
+ export const SINGLETON_RESOURCES = [
50
+ 'me',
51
+ 'self',
52
+ 'current',
53
+ 'profile',
54
+ 'health',
55
+ 'ready',
56
+ 'status',
57
+ 'info',
58
+ 'version',
59
+ 'metadata',
60
+ 'config',
61
+ 'settings',
62
+ 'preferences',
63
+ 'summary',
64
+ 'stats',
65
+ 'statistics',
66
+ 'dashboard',
67
+ 'schema',
68
+ 'spec',
69
+ 'openapi'
70
+ ];
71
+
72
+ /**
73
+ * Collection action verbs
74
+ * Actions that operate on collections (e.g., /activities/search)
75
+ */
76
+ export const COLLECTION_ACTIONS = [
77
+ 'search',
78
+ 'export',
79
+ 'import',
80
+ 'bulk',
81
+ 'batch',
82
+ 'count',
83
+ 'stats',
84
+ 'validate'
85
+ ];
86
+
87
+ /**
88
+ * Special actions - combination of auth, health, and user profile endpoints
89
+ * These endpoints get special naming treatment and keep their action verb
90
+ */
91
+ export const SPECIAL_ACTIONS = [
92
+ ...AUTH_ACTIONS,
93
+ ...HEALTH_ENDPOINTS,
94
+ ...USER_PROFILE_ENDPOINTS,
95
+ 'check',
96
+ 'verify',
97
+ 'validate'
98
+ ];
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Shared naming utilities for OpenAPI code generation
3
+ * Handles singular/plural conversions with proper edge case handling
4
+ */
5
+
6
+ /**
7
+ * Map of irregular plural forms
8
+ */
9
+ export const IRREGULAR_PLURALS = {
10
+ person: 'people',
11
+ child: 'children',
12
+ data: 'data', // already plural
13
+ media: 'media', // already plural
14
+ metadata: 'metadata', // already plural
15
+ people: 'people', // already plural
16
+ children: 'children', // already plural
17
+ };
18
+
19
+ /**
20
+ * Map of irregular singular forms (reverse lookup)
21
+ */
22
+ const IRREGULAR_SINGULARS = {
23
+ people: 'person',
24
+ children: 'child',
25
+ data: 'data', // already singular
26
+ media: 'media', // already singular
27
+ metadata: 'metadata', // already singular
28
+ person: 'person', // already singular
29
+ child: 'child', // already singular
30
+ };
31
+
32
+ /**
33
+ * Words that naturally end in 's' but are singular
34
+ */
35
+ const SINGULAR_EXCEPTIONS = [
36
+ 'status',
37
+ 'class',
38
+ 'address',
39
+ 'access',
40
+ 'success',
41
+ 'process',
42
+ 'bus',
43
+ 'focus',
44
+ 'virus',
45
+ ];
46
+
47
+ /**
48
+ * Check if a word is already plural
49
+ * @param {string} word - The word to check
50
+ * @returns {boolean} - True if the word is plural
51
+ */
52
+ export function isPlural(word) {
53
+ // Check irregular plurals first
54
+ if (IRREGULAR_PLURALS[word.toLowerCase()]) {
55
+ return IRREGULAR_PLURALS[word.toLowerCase()] === word.toLowerCase();
56
+ }
57
+
58
+ // Check singular exceptions - these end in 's' but are not plural
59
+ if (SINGULAR_EXCEPTIONS.includes(word.toLowerCase())) {
60
+ return false;
61
+ }
62
+
63
+ // Check common plural endings
64
+ if (word.endsWith('ies') || word.endsWith('es') || word.endsWith('s')) {
65
+ return true;
66
+ }
67
+
68
+ return false;
69
+ }
70
+
71
+ /**
72
+ * Convert plural word to singular
73
+ * @param {string} word - The word to singularize
74
+ * @returns {string} - The singular form of the word
75
+ */
76
+ export function singularize(word) {
77
+ // Handle empty or short words
78
+ if (!word || word.length <= 1) {
79
+ return word;
80
+ }
81
+
82
+ // Check irregular singulars first
83
+ const lowerWord = word.toLowerCase();
84
+ if (IRREGULAR_SINGULARS[lowerWord]) {
85
+ return IRREGULAR_SINGULARS[lowerWord];
86
+ }
87
+
88
+ // Already singular (check exceptions)
89
+ if (SINGULAR_EXCEPTIONS.includes(lowerWord)) {
90
+ return word;
91
+ }
92
+
93
+ // -ies → -y (activities → activity)
94
+ if (word.endsWith('ies')) {
95
+ return word.slice(0, -3) + 'y';
96
+ }
97
+
98
+ // -ses → -s (processes → process, but 'process' is already singular)
99
+ // -xes → -x (boxes → box)
100
+ // -ches → -ch (watches → watch)
101
+ // -shes → -sh (dishes → dish)
102
+ if (word.endsWith('ses') || word.endsWith('xes') || word.endsWith('ches') || word.endsWith('shes')) {
103
+ return word.slice(0, -2);
104
+ }
105
+
106
+ // Regular -s ending (cats → cat)
107
+ // But avoid words ending in -ss (class, success, etc.)
108
+ if (word.endsWith('s') && !word.endsWith('ss') && word.length > 1) {
109
+ return word.slice(0, -1);
110
+ }
111
+
112
+ return word;
113
+ }
114
+
115
+ /**
116
+ * Convert singular word to plural
117
+ * @param {string} word - The word to pluralize
118
+ * @returns {string} - The plural form of the word
119
+ */
120
+ export function pluralize(word) {
121
+ // Handle empty or short words
122
+ if (!word || word.length <= 1) {
123
+ return word;
124
+ }
125
+
126
+ // Check if already plural
127
+ if (isPlural(word)) {
128
+ return word;
129
+ }
130
+
131
+ // Check irregular plurals first
132
+ const lowerWord = word.toLowerCase();
133
+ if (IRREGULAR_PLURALS[lowerWord]) {
134
+ return IRREGULAR_PLURALS[lowerWord];
135
+ }
136
+
137
+ // -y ending (not vowel+y) → -ies (activity → activities)
138
+ if (word.endsWith('y') && !['ay', 'ey', 'iy', 'oy', 'uy'].includes(word.slice(-2))) {
139
+ return word.slice(0, -1) + 'ies';
140
+ }
141
+
142
+ // -x, -ch, -sh endings → add 'es' (box → boxes, watch → watches, dish → dishes)
143
+ if (word.endsWith('x') || word.endsWith('ch') || word.endsWith('sh')) {
144
+ return word + 'es';
145
+ }
146
+
147
+ // Regular: add 's' (cat → cats, dog → dogs)
148
+ return word + 's';
149
+ }
@@ -1,7 +1,7 @@
1
1
  import React from "react";
2
2
  import { type VariantProps } from "class-variance-authority";
3
3
  declare const badgeVariants: (props?: {
4
- variant?: "success" | "secondary" | "default" | "info" | "warning" | "destructive" | "outline";
4
+ variant?: "success" | "secondary" | "warning" | "info" | "default" | "destructive" | "outline";
5
5
  size?: "sm" | "lg" | "default";
6
6
  } & import("class-variance-authority/types").ClassProp) => string;
7
7
  export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {
@@ -1 +1 @@
1
- {"version":3,"file":"DataTable.d.ts","sourceRoot":"","sources":["../../../../../src/atoms/components/data/DataTable/DataTable.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AASpD,OAAO,KAAK,EAGV,cAAc,EACf,MAAM,mBAAmB,CAAC;AAK3B,YAAY,EACV,MAAM,EACN,gBAAgB,EAChB,cAAc,GACf,MAAM,mBAAmB,CAAC;AAI3B,wBAAgB,SAAS,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAC3D,IAAI,EACJ,OAAO,EACP,iBAA+B,EAC/B,QAAa,EACb,cAAqB,EACrB,UAAiB,EACjB,UAAU,EACV,YAAkC,EAClC,SAAc,EACd,KAAa,EACb,SAAiB,EACjB,gBAAoB,EACpB,UAAiB,EACjB,gBAAgB,EAChB,EAAE,GACH,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,aAAa,2CA0XnC"}
1
+ {"version":3,"file":"DataTable.d.ts","sourceRoot":"","sources":["../../../../../src/atoms/components/data/DataTable/DataTable.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AASpD,OAAO,KAAK,EAGV,cAAc,EACf,MAAM,mBAAmB,CAAC;AAK3B,YAAY,EACV,MAAM,EACN,gBAAgB,EAChB,cAAc,GACf,MAAM,mBAAmB,CAAC;AAI3B,wBAAgB,SAAS,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAC3D,IAAI,EACJ,OAAO,EACP,iBAA+B,EAC/B,QAAa,EACb,cAAqB,EACrB,UAAiB,EACjB,UAAU,EACV,YAAkC,EAClC,SAAc,EACd,KAAa,EACb,SAAiB,EACjB,gBAAoB,EACpB,UAAiB,EACjB,gBAAgB,EAChB,EAAE,GACH,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,aAAa,2CA4YnC"}
@@ -1,5 +1,5 @@
1
1
  import type { ResponsiveColumnConfig, DisplayMode, ResponsiveValue, Breakpoint } from "../../../config/responsive";
2
- import type { UIConfig } from "../../../types/ui-config";
2
+ import type { UIConfig, UIFieldType, FieldFormat } from "../../../types/ui-config";
3
3
  export interface Column<T> {
4
4
  key: string;
5
5
  header: string | React.ReactNode;
@@ -7,8 +7,20 @@ export interface Column<T> {
7
7
  sortable?: boolean;
8
8
  filterable?: boolean;
9
9
  width?: string;
10
- /** Auto-render badges for status/category columns */
11
- type?: "status" | "category" | "default";
10
+ /**
11
+ * Field type for automatic rendering.
12
+ * When set, DataTable uses defaultFieldRenderers to format the cell.
13
+ * Overridden by custom `cell` renderer if provided.
14
+ *
15
+ * Supports all UIFieldType values: status, category, money, percent,
16
+ * date, datetime, user, entity, boolean, text, number, email, url, phone.
17
+ */
18
+ type?: UIFieldType;
19
+ /**
20
+ * Format configuration for typed fields.
21
+ * Used by money (currency, decimals, locale), date (locale), status (statusColors), etc.
22
+ */
23
+ format?: FieldFormat;
12
24
  }
13
25
  export interface ResponsiveColumn<T> extends Column<T>, ResponsiveColumnConfig {
14
26
  /** Responsive width adjustments */