@fgv/ts-res-ui-components 5.0.0-10

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 (231) hide show
  1. package/.rush/temp/03c8b056281d9db0a97d8a6e25eea798a160d393.tar.log +271 -0
  2. package/.rush/temp/chunked-rush-logs/ts-res-ui-components.build.chunks.jsonl +9 -0
  3. package/.rush/temp/operation/build/all.log +9 -0
  4. package/.rush/temp/operation/build/log-chunks.jsonl +9 -0
  5. package/.rush/temp/operation/build/state.json +3 -0
  6. package/.rush/temp/shrinkwrap-deps.json +1111 -0
  7. package/README.md +18 -0
  8. package/REFACTORING_PLAN.md +171 -0
  9. package/config/jest.config.json +16 -0
  10. package/config/jest.setup.js +64 -0
  11. package/config/rig.json +16 -0
  12. package/lib/components/common/QualifierContextControl.d.ts +14 -0
  13. package/lib/components/common/QualifierContextControl.d.ts.map +1 -0
  14. package/lib/components/common/QualifierContextControl.js +78 -0
  15. package/lib/components/common/QualifierContextControl.js.map +1 -0
  16. package/lib/components/common/ResourceListView.d.ts +11 -0
  17. package/lib/components/common/ResourceListView.d.ts.map +1 -0
  18. package/lib/components/common/ResourceListView.js +20 -0
  19. package/lib/components/common/ResourceListView.js.map +1 -0
  20. package/lib/components/common/ResourceTreeView.d.ts +12 -0
  21. package/lib/components/common/ResourceTreeView.d.ts.map +1 -0
  22. package/lib/components/common/ResourceTreeView.js +162 -0
  23. package/lib/components/common/ResourceTreeView.js.map +1 -0
  24. package/lib/components/forms/HierarchyEditor.d.ts +10 -0
  25. package/lib/components/forms/HierarchyEditor.d.ts.map +1 -0
  26. package/lib/components/forms/HierarchyEditor.js +106 -0
  27. package/lib/components/forms/HierarchyEditor.js.map +1 -0
  28. package/lib/components/forms/QualifierEditForm.d.ts +11 -0
  29. package/lib/components/forms/QualifierEditForm.d.ts.map +1 -0
  30. package/lib/components/forms/QualifierEditForm.js +181 -0
  31. package/lib/components/forms/QualifierEditForm.js.map +1 -0
  32. package/lib/components/forms/QualifierTypeEditForm.d.ts +10 -0
  33. package/lib/components/forms/QualifierTypeEditForm.d.ts.map +1 -0
  34. package/lib/components/forms/QualifierTypeEditForm.js +172 -0
  35. package/lib/components/forms/QualifierTypeEditForm.js.map +1 -0
  36. package/lib/components/forms/ResourceTypeEditForm.d.ts +10 -0
  37. package/lib/components/forms/ResourceTypeEditForm.d.ts.map +1 -0
  38. package/lib/components/forms/ResourceTypeEditForm.js +188 -0
  39. package/lib/components/forms/ResourceTypeEditForm.js.map +1 -0
  40. package/lib/components/forms/index.d.ts +9 -0
  41. package/lib/components/forms/index.d.ts.map +1 -0
  42. package/lib/components/forms/index.js +5 -0
  43. package/lib/components/forms/index.js.map +1 -0
  44. package/lib/components/orchestrator/ResourceOrchestrator.d.ts +14 -0
  45. package/lib/components/orchestrator/ResourceOrchestrator.d.ts.map +1 -0
  46. package/lib/components/orchestrator/ResourceOrchestrator.js +278 -0
  47. package/lib/components/orchestrator/ResourceOrchestrator.js.map +1 -0
  48. package/lib/components/views/CompiledView/index.d.ts +5 -0
  49. package/lib/components/views/CompiledView/index.d.ts.map +1 -0
  50. package/lib/components/views/CompiledView/index.js +595 -0
  51. package/lib/components/views/CompiledView/index.js.map +1 -0
  52. package/lib/components/views/ConfigurationView/index.d.ts +5 -0
  53. package/lib/components/views/ConfigurationView/index.d.ts.map +1 -0
  54. package/lib/components/views/ConfigurationView/index.js +363 -0
  55. package/lib/components/views/ConfigurationView/index.js.map +1 -0
  56. package/lib/components/views/FilterView/index.d.ts +5 -0
  57. package/lib/components/views/FilterView/index.d.ts.map +1 -0
  58. package/lib/components/views/FilterView/index.js +463 -0
  59. package/lib/components/views/FilterView/index.js.map +1 -0
  60. package/lib/components/views/ImportView/index.d.ts +5 -0
  61. package/lib/components/views/ImportView/index.d.ts.map +1 -0
  62. package/lib/components/views/ImportView/index.js +514 -0
  63. package/lib/components/views/ImportView/index.js.map +1 -0
  64. package/lib/components/views/ResolutionView/EditableJsonView.d.ts +21 -0
  65. package/lib/components/views/ResolutionView/EditableJsonView.d.ts.map +1 -0
  66. package/lib/components/views/ResolutionView/EditableJsonView.js +109 -0
  67. package/lib/components/views/ResolutionView/EditableJsonView.js.map +1 -0
  68. package/lib/components/views/ResolutionView/ResolutionEditControls.d.ts +19 -0
  69. package/lib/components/views/ResolutionView/ResolutionEditControls.d.ts.map +1 -0
  70. package/lib/components/views/ResolutionView/ResolutionEditControls.js +82 -0
  71. package/lib/components/views/ResolutionView/ResolutionEditControls.js.map +1 -0
  72. package/lib/components/views/ResolutionView/index.d.ts +5 -0
  73. package/lib/components/views/ResolutionView/index.d.ts.map +1 -0
  74. package/lib/components/views/ResolutionView/index.js +255 -0
  75. package/lib/components/views/ResolutionView/index.js.map +1 -0
  76. package/lib/components/views/SourceView/index.d.ts +5 -0
  77. package/lib/components/views/SourceView/index.d.ts.map +1 -0
  78. package/lib/components/views/SourceView/index.js +316 -0
  79. package/lib/components/views/SourceView/index.js.map +1 -0
  80. package/lib/components/views/ZipLoaderView/index.d.ts +5 -0
  81. package/lib/components/views/ZipLoaderView/index.d.ts.map +1 -0
  82. package/lib/components/views/ZipLoaderView/index.js +313 -0
  83. package/lib/components/views/ZipLoaderView/index.js.map +1 -0
  84. package/lib/hooks/useConfigurationState.d.ts +46 -0
  85. package/lib/hooks/useConfigurationState.d.ts.map +1 -0
  86. package/lib/hooks/useConfigurationState.js +239 -0
  87. package/lib/hooks/useConfigurationState.js.map +1 -0
  88. package/lib/hooks/useFilterState.d.ts +7 -0
  89. package/lib/hooks/useFilterState.d.ts.map +1 -0
  90. package/lib/hooks/useFilterState.js +80 -0
  91. package/lib/hooks/useFilterState.js.map +1 -0
  92. package/lib/hooks/useResolutionState.d.ts +8 -0
  93. package/lib/hooks/useResolutionState.d.ts.map +1 -0
  94. package/lib/hooks/useResolutionState.js +253 -0
  95. package/lib/hooks/useResolutionState.js.map +1 -0
  96. package/lib/hooks/useResourceData.d.ts +19 -0
  97. package/lib/hooks/useResourceData.d.ts.map +1 -0
  98. package/lib/hooks/useResourceData.js +368 -0
  99. package/lib/hooks/useResourceData.js.map +1 -0
  100. package/lib/hooks/useViewState.d.ts +10 -0
  101. package/lib/hooks/useViewState.d.ts.map +1 -0
  102. package/lib/hooks/useViewState.js +29 -0
  103. package/lib/hooks/useViewState.js.map +1 -0
  104. package/lib/index.d.ts +27 -0
  105. package/lib/index.d.ts.map +1 -0
  106. package/lib/index.js +34 -0
  107. package/lib/index.js.map +1 -0
  108. package/lib/test/helpers/testDataLoader.d.ts +37 -0
  109. package/lib/test/helpers/testDataLoader.d.ts.map +1 -0
  110. package/lib/test/helpers/testDataLoader.js +171 -0
  111. package/lib/test/helpers/testDataLoader.js.map +1 -0
  112. package/lib/test/unit/utils/configurationUtils.test.d.ts +2 -0
  113. package/lib/test/unit/utils/configurationUtils.test.d.ts.map +1 -0
  114. package/lib/test/unit/utils/configurationUtils.test.js +497 -0
  115. package/lib/test/unit/utils/configurationUtils.test.js.map +1 -0
  116. package/lib/test/unit/utils/fileProcessing.test.d.ts +2 -0
  117. package/lib/test/unit/utils/fileProcessing.test.d.ts.map +1 -0
  118. package/lib/test/unit/utils/fileProcessing.test.js +321 -0
  119. package/lib/test/unit/utils/fileProcessing.test.js.map +1 -0
  120. package/lib/test/unit/utils/filterResources.test.d.ts +2 -0
  121. package/lib/test/unit/utils/filterResources.test.d.ts.map +1 -0
  122. package/lib/test/unit/utils/filterResources.test.js +403 -0
  123. package/lib/test/unit/utils/filterResources.test.js.map +1 -0
  124. package/lib/test/unit/utils/resolutionEditing.test.d.ts +2 -0
  125. package/lib/test/unit/utils/resolutionEditing.test.d.ts.map +1 -0
  126. package/lib/test/unit/utils/resolutionEditing.test.js +439 -0
  127. package/lib/test/unit/utils/resolutionEditing.test.js.map +1 -0
  128. package/lib/test/unit/utils/resolutionUtils.test.d.ts +2 -0
  129. package/lib/test/unit/utils/resolutionUtils.test.d.ts.map +1 -0
  130. package/lib/test/unit/utils/resolutionUtils.test.js +397 -0
  131. package/lib/test/unit/utils/resolutionUtils.test.js.map +1 -0
  132. package/lib/test/unit/utils/tsResIntegration.test.d.ts +2 -0
  133. package/lib/test/unit/utils/tsResIntegration.test.d.ts.map +1 -0
  134. package/lib/test/unit/utils/tsResIntegration.test.js +376 -0
  135. package/lib/test/unit/utils/tsResIntegration.test.js.map +1 -0
  136. package/lib/types/index.d.ts +251 -0
  137. package/lib/types/index.d.ts.map +1 -0
  138. package/lib/types/index.js +2 -0
  139. package/lib/types/index.js.map +1 -0
  140. package/lib/utils/configurationUtils.d.ts +74 -0
  141. package/lib/utils/configurationUtils.d.ts.map +1 -0
  142. package/lib/utils/configurationUtils.js +359 -0
  143. package/lib/utils/configurationUtils.js.map +1 -0
  144. package/lib/utils/fileProcessing.d.ts +18 -0
  145. package/lib/utils/fileProcessing.d.ts.map +1 -0
  146. package/lib/utils/fileProcessing.js +142 -0
  147. package/lib/utils/fileProcessing.js.map +1 -0
  148. package/lib/utils/filterResources.d.ts +38 -0
  149. package/lib/utils/filterResources.d.ts.map +1 -0
  150. package/lib/utils/filterResources.js +153 -0
  151. package/lib/utils/filterResources.js.map +1 -0
  152. package/lib/utils/resolutionEditing.d.ts +58 -0
  153. package/lib/utils/resolutionEditing.d.ts.map +1 -0
  154. package/lib/utils/resolutionEditing.js +246 -0
  155. package/lib/utils/resolutionEditing.js.map +1 -0
  156. package/lib/utils/resolutionUtils.d.ts +28 -0
  157. package/lib/utils/resolutionUtils.d.ts.map +1 -0
  158. package/lib/utils/resolutionUtils.js +216 -0
  159. package/lib/utils/resolutionUtils.js.map +1 -0
  160. package/lib/utils/tsResIntegration.d.ts +71 -0
  161. package/lib/utils/tsResIntegration.d.ts.map +1 -0
  162. package/lib/utils/tsResIntegration.js +294 -0
  163. package/lib/utils/tsResIntegration.js.map +1 -0
  164. package/lib/utils/zipLoader/browserZipLoader.d.ts +48 -0
  165. package/lib/utils/zipLoader/browserZipLoader.d.ts.map +1 -0
  166. package/lib/utils/zipLoader/browserZipLoader.js +247 -0
  167. package/lib/utils/zipLoader/browserZipLoader.js.map +1 -0
  168. package/lib/utils/zipLoader/index.d.ts +8 -0
  169. package/lib/utils/zipLoader/index.d.ts.map +1 -0
  170. package/lib/utils/zipLoader/index.js +13 -0
  171. package/lib/utils/zipLoader/index.js.map +1 -0
  172. package/lib/utils/zipLoader/nodeZipBuilder.d.ts +55 -0
  173. package/lib/utils/zipLoader/nodeZipBuilder.d.ts.map +1 -0
  174. package/lib/utils/zipLoader/nodeZipBuilder.js +98 -0
  175. package/lib/utils/zipLoader/nodeZipBuilder.js.map +1 -0
  176. package/lib/utils/zipLoader/types.d.ts +139 -0
  177. package/lib/utils/zipLoader/types.d.ts.map +1 -0
  178. package/lib/utils/zipLoader/types.js +2 -0
  179. package/lib/utils/zipLoader/types.js.map +1 -0
  180. package/lib/utils/zipLoader/zipUtils.d.ts +53 -0
  181. package/lib/utils/zipLoader/zipUtils.d.ts.map +1 -0
  182. package/lib/utils/zipLoader/zipUtils.js +229 -0
  183. package/lib/utils/zipLoader/zipUtils.js.map +1 -0
  184. package/package.json +69 -0
  185. package/rush-logs/ts-res-ui-components.build.cache.log +3 -0
  186. package/rush-logs/ts-res-ui-components.build.log +9 -0
  187. package/src/components/common/QualifierContextControl.tsx +151 -0
  188. package/src/components/common/ResourceListView.tsx +63 -0
  189. package/src/components/common/ResourceTreeView.tsx +271 -0
  190. package/src/components/forms/HierarchyEditor.tsx +204 -0
  191. package/src/components/forms/QualifierEditForm.tsx +355 -0
  192. package/src/components/forms/QualifierTypeEditForm.tsx +347 -0
  193. package/src/components/forms/ResourceTypeEditForm.tsx +331 -0
  194. package/src/components/forms/index.ts +11 -0
  195. package/src/components/orchestrator/ResourceOrchestrator.tsx +372 -0
  196. package/src/components/views/CompiledView/index.tsx +922 -0
  197. package/src/components/views/ConfigurationView/index.tsx +800 -0
  198. package/src/components/views/FilterView/index.tsx +825 -0
  199. package/src/components/views/ImportView/index.tsx +717 -0
  200. package/src/components/views/ResolutionView/EditableJsonView.tsx +214 -0
  201. package/src/components/views/ResolutionView/ResolutionEditControls.tsx +170 -0
  202. package/src/components/views/ResolutionView/index.tsx +591 -0
  203. package/src/components/views/SourceView/index.tsx +536 -0
  204. package/src/components/views/ZipLoaderView/index.tsx +485 -0
  205. package/src/hooks/useConfigurationState.ts +374 -0
  206. package/src/hooks/useFilterState.ts +97 -0
  207. package/src/hooks/useResolutionState.ts +355 -0
  208. package/src/hooks/useResourceData.ts +467 -0
  209. package/src/hooks/useViewState.ts +44 -0
  210. package/src/index.ts +45 -0
  211. package/src/test/helpers/testDataLoader.ts +195 -0
  212. package/src/test/unit/utils/configurationUtils.test.ts +630 -0
  213. package/src/test/unit/utils/fileProcessing.test.ts +391 -0
  214. package/src/test/unit/utils/filterResources.test.ts +574 -0
  215. package/src/test/unit/utils/resolutionEditing.test.ts +556 -0
  216. package/src/test/unit/utils/resolutionUtils.test.ts +521 -0
  217. package/src/test/unit/utils/tsResIntegration.test.ts +433 -0
  218. package/src/types/index.ts +322 -0
  219. package/src/utils/configurationUtils.ts +424 -0
  220. package/src/utils/fileProcessing.ts +160 -0
  221. package/src/utils/filterResources.ts +206 -0
  222. package/src/utils/resolutionEditing.ts +319 -0
  223. package/src/utils/resolutionUtils.ts +289 -0
  224. package/src/utils/tsResIntegration.ts +440 -0
  225. package/src/utils/zipLoader/browserZipLoader.ts +319 -0
  226. package/src/utils/zipLoader/index.ts +26 -0
  227. package/src/utils/zipLoader/nodeZipBuilder.ts +153 -0
  228. package/src/utils/zipLoader/types.ts +175 -0
  229. package/src/utils/zipLoader/zipUtils.ts +266 -0
  230. package/temp/build/typescript/ts_gZid87Hu.json +1 -0
  231. package/tsconfig.json +15 -0
@@ -0,0 +1,355 @@
1
+ import React, { useState, useCallback } from 'react';
2
+ import { XMarkIcon, InformationCircleIcon } from '@heroicons/react/24/outline';
3
+ import { Qualifiers, QualifierTypes } from '@fgv/ts-res';
4
+
5
+ export interface QualifierEditFormProps {
6
+ qualifier?: Qualifiers.IQualifierDecl;
7
+ qualifierTypes: QualifierTypes.Config.ISystemQualifierTypeConfig[];
8
+ onSave: (qualifier: Qualifiers.IQualifierDecl) => void;
9
+ onCancel: () => void;
10
+ existingNames?: string[];
11
+ }
12
+
13
+ interface FormData {
14
+ name: string;
15
+ typeName: string;
16
+ defaultPriority: number;
17
+ token: string;
18
+ tokenIsOptional: boolean;
19
+ defaultValue: string;
20
+ }
21
+
22
+ export const QualifierEditForm: React.FC<QualifierEditFormProps> = ({
23
+ qualifier,
24
+ qualifierTypes,
25
+ onSave,
26
+ onCancel,
27
+ existingNames = []
28
+ }) => {
29
+ const [formData, setFormData] = useState<FormData>(() => {
30
+ if (qualifier) {
31
+ return {
32
+ name: qualifier.name,
33
+ typeName: qualifier.typeName,
34
+ defaultPriority: qualifier.defaultPriority,
35
+ token: qualifier.token || '',
36
+ tokenIsOptional: qualifier.tokenIsOptional || false,
37
+ defaultValue: qualifier.defaultValue || ''
38
+ };
39
+ }
40
+ return {
41
+ name: '',
42
+ typeName: qualifierTypes[0]?.name || '',
43
+ defaultPriority: 50,
44
+ token: '',
45
+ tokenIsOptional: false,
46
+ defaultValue: ''
47
+ };
48
+ });
49
+
50
+ const [errors, setErrors] = useState<Record<string, string>>({});
51
+
52
+ // Get the selected qualifier type for context
53
+ const selectedQualifierType = qualifierTypes.find((qt) => qt.name === formData.typeName);
54
+ const allowsContextList =
55
+ selectedQualifierType?.configuration && (selectedQualifierType.configuration as any).allowContextList;
56
+
57
+ // Validation
58
+ const validateForm = useCallback((): boolean => {
59
+ const newErrors: Record<string, string> = {};
60
+
61
+ if (!formData.name.trim()) {
62
+ newErrors.name = 'Name is required';
63
+ } else if (existingNames.includes(formData.name) && formData.name !== qualifier?.name) {
64
+ newErrors.name = 'Name must be unique';
65
+ }
66
+
67
+ if (!formData.typeName) {
68
+ newErrors.typeName = 'Qualifier type is required';
69
+ }
70
+
71
+ if (formData.defaultPriority < 0 || formData.defaultPriority > 1000) {
72
+ newErrors.defaultPriority = 'Priority must be between 0 and 1000';
73
+ }
74
+
75
+ if (formData.token && !/^[a-zA-Z][a-zA-Z0-9_]*$/.test(formData.token)) {
76
+ newErrors.token = 'Token must start with a letter and contain only letters, numbers, and underscores';
77
+ }
78
+
79
+ setErrors(newErrors);
80
+ return Object.keys(newErrors).length === 0;
81
+ }, [formData, existingNames, qualifier?.name]);
82
+
83
+ const handleSave = useCallback(() => {
84
+ if (!validateForm()) return;
85
+
86
+ const result: Qualifiers.IQualifierDecl = {
87
+ name: formData.name,
88
+ typeName: formData.typeName,
89
+ defaultPriority: formData.defaultPriority,
90
+ ...(formData.token && { token: formData.token }),
91
+ ...(formData.token && formData.tokenIsOptional && { tokenIsOptional: true }),
92
+ ...(formData.defaultValue && { defaultValue: formData.defaultValue })
93
+ };
94
+
95
+ onSave(result);
96
+ }, [formData, validateForm, onSave]);
97
+
98
+ const updateField = useCallback(
99
+ (field: keyof FormData, value: any) => {
100
+ setFormData((prev) => {
101
+ const updated = { ...prev, [field]: value };
102
+
103
+ // Auto-generate token from name if no custom token is set
104
+ if (field === 'name' && !prev.token) {
105
+ updated.token = value.toLowerCase().replace(/[^a-zA-Z0-9]/g, '');
106
+ }
107
+
108
+ // Clear tokenIsOptional if token is cleared
109
+ if (field === 'token' && !value) {
110
+ updated.tokenIsOptional = false;
111
+ }
112
+
113
+ return updated;
114
+ });
115
+
116
+ if (errors[field]) {
117
+ setErrors((prev) => ({ ...prev, [field]: '' }));
118
+ }
119
+ },
120
+ [errors]
121
+ );
122
+
123
+ const getDefaultValuePlaceholder = (): string => {
124
+ if (!selectedQualifierType) return 'Enter default value';
125
+
126
+ switch (selectedQualifierType.systemType) {
127
+ case 'language':
128
+ return allowsContextList ? 'e.g., en-US or en-US,en' : 'e.g., en-US';
129
+ case 'territory':
130
+ return allowsContextList ? 'e.g., US or US,CA' : 'e.g., US';
131
+ case 'literal':
132
+ const enumValues = (selectedQualifierType.configuration as any)?.enumeratedValues;
133
+ if (enumValues && enumValues.length > 0) {
134
+ return allowsContextList
135
+ ? `e.g., ${enumValues[0]} or ${enumValues.slice(0, 2).join(',')}`
136
+ : `e.g., ${enumValues[0]}`;
137
+ }
138
+ return allowsContextList ? 'e.g., value or value1,value2' : 'e.g., value';
139
+ default:
140
+ return 'Enter default value';
141
+ }
142
+ };
143
+
144
+ return (
145
+ <div className="fixed inset-0 bg-gray-600 bg-opacity-50 flex items-center justify-center z-50 p-4">
146
+ <div className="bg-white rounded-lg shadow-xl max-w-2xl w-full h-full max-h-[calc(100vh-2rem)] flex flex-col">
147
+ {/* Fixed Header */}
148
+ <div className="flex items-center justify-between p-6 border-b flex-shrink-0">
149
+ <h3 className="text-lg font-medium text-gray-900">
150
+ {qualifier ? 'Edit Qualifier' : 'Add Qualifier'}
151
+ </h3>
152
+ <button onClick={onCancel} className="text-gray-400 hover:text-gray-600">
153
+ <XMarkIcon className="w-6 h-6" />
154
+ </button>
155
+ </div>
156
+
157
+ {/* Scrollable Content */}
158
+ <div className="p-6 space-y-6 overflow-y-auto flex-1 min-h-0">
159
+ {/* Basic Properties */}
160
+ <div className="grid grid-cols-2 gap-4">
161
+ <div>
162
+ <label className="block text-sm font-medium text-gray-700 mb-1">Name *</label>
163
+ <input
164
+ type="text"
165
+ value={formData.name}
166
+ onChange={(e) => updateField('name', e.target.value)}
167
+ className={`w-full px-3 py-2 border rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 ${
168
+ errors.name ? 'border-red-300' : 'border-gray-300'
169
+ }`}
170
+ placeholder="Enter qualifier name"
171
+ />
172
+ {errors.name && <p className="mt-1 text-sm text-red-600">{errors.name}</p>}
173
+ </div>
174
+
175
+ <div>
176
+ <label className="block text-sm font-medium text-gray-700 mb-1">Qualifier Type *</label>
177
+ <select
178
+ value={formData.typeName}
179
+ onChange={(e) => updateField('typeName', e.target.value)}
180
+ className={`w-full px-3 py-2 border rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 ${
181
+ errors.typeName ? 'border-red-300' : 'border-gray-300'
182
+ }`}
183
+ >
184
+ {qualifierTypes.length === 0 ? (
185
+ <option value="">No qualifier types available</option>
186
+ ) : (
187
+ qualifierTypes.map((type) => (
188
+ <option key={type.name} value={type.name}>
189
+ {type.name} ({type.systemType})
190
+ </option>
191
+ ))
192
+ )}
193
+ </select>
194
+ {errors.typeName && <p className="mt-1 text-sm text-red-600">{errors.typeName}</p>}
195
+ </div>
196
+ </div>
197
+
198
+ <div className="grid grid-cols-2 gap-4">
199
+ <div>
200
+ <label className="block text-sm font-medium text-gray-700 mb-1">Default Priority *</label>
201
+ <input
202
+ type="number"
203
+ min="0"
204
+ max="1000"
205
+ value={formData.defaultPriority}
206
+ onChange={(e) => updateField('defaultPriority', parseInt(e.target.value) || 0)}
207
+ className={`w-full px-3 py-2 border rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 ${
208
+ errors.defaultPriority ? 'border-red-300' : 'border-gray-300'
209
+ }`}
210
+ placeholder="50"
211
+ />
212
+ {errors.defaultPriority && (
213
+ <p className="mt-1 text-sm text-red-600">{errors.defaultPriority}</p>
214
+ )}
215
+ <p className="mt-1 text-xs text-gray-500">Higher numbers have higher priority (0-1000)</p>
216
+ </div>
217
+
218
+ <div>
219
+ <label className="block text-sm font-medium text-gray-700 mb-1">
220
+ Token
221
+ <span className="ml-1 text-gray-500">(optional)</span>
222
+ </label>
223
+ <input
224
+ type="text"
225
+ value={formData.token}
226
+ onChange={(e) => updateField('token', e.target.value)}
227
+ className={`w-full px-3 py-2 border rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 ${
228
+ errors.token ? 'border-red-300' : 'border-gray-300'
229
+ }`}
230
+ placeholder="e.g., lang, locale"
231
+ />
232
+ {errors.token && <p className="mt-1 text-sm text-red-600">{errors.token}</p>}
233
+ <p className="mt-1 text-xs text-gray-500">Used to identify this qualifier in resource names</p>
234
+ </div>
235
+ </div>
236
+
237
+ {/* Token Options */}
238
+ {formData.token && (
239
+ <div className="flex items-center">
240
+ <input
241
+ type="checkbox"
242
+ id="tokenIsOptional"
243
+ checked={formData.tokenIsOptional}
244
+ onChange={(e) => updateField('tokenIsOptional', e.target.checked)}
245
+ className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
246
+ />
247
+ <label htmlFor="tokenIsOptional" className="ml-2 text-sm text-gray-700">
248
+ Token is optional in resource names
249
+ </label>
250
+ <div className="ml-2 group relative">
251
+ <InformationCircleIcon className="w-4 h-4 text-gray-400" />
252
+ <div className="absolute left-0 bottom-6 hidden group-hover:block bg-gray-800 text-white text-xs rounded py-1 px-2 whitespace-nowrap z-10">
253
+ Allow resources without this qualifier token
254
+ </div>
255
+ </div>
256
+ </div>
257
+ )}
258
+
259
+ {/* Default Value */}
260
+ <div>
261
+ <label className="block text-sm font-medium text-gray-700 mb-1">
262
+ Default Value
263
+ <span className="ml-1 text-gray-500">(optional)</span>
264
+ </label>
265
+ <input
266
+ type="text"
267
+ value={formData.defaultValue}
268
+ onChange={(e) => updateField('defaultValue', e.target.value)}
269
+ className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500"
270
+ placeholder={getDefaultValuePlaceholder()}
271
+ />
272
+ <div className="mt-1 text-xs text-gray-500">
273
+ {selectedQualifierType && (
274
+ <div>
275
+ <p>
276
+ Qualifier type: <span className="font-medium">{selectedQualifierType.systemType}</span>
277
+ </p>
278
+ {allowsContextList && (
279
+ <p className="text-blue-600">
280
+ This qualifier type supports multiple values (comma-separated)
281
+ </p>
282
+ )}
283
+ </div>
284
+ )}
285
+ </div>
286
+ </div>
287
+
288
+ {/* Qualifier Type Information */}
289
+ {selectedQualifierType && (
290
+ <div className="p-4 bg-gray-50 rounded-lg">
291
+ <h4 className="font-medium text-gray-900 mb-2">Qualifier Type Information</h4>
292
+ <div className="text-sm text-gray-600 space-y-1">
293
+ <p>
294
+ <span className="font-medium">System Type:</span> {selectedQualifierType.systemType}
295
+ </p>
296
+ <p>
297
+ <span className="font-medium">Supports Context List:</span>{' '}
298
+ {allowsContextList ? 'Yes' : 'No'}
299
+ </p>
300
+ {selectedQualifierType.systemType === 'literal' && selectedQualifierType.configuration && (
301
+ <>
302
+ {(selectedQualifierType.configuration as any).caseSensitive !== undefined && (
303
+ <p>
304
+ <span className="font-medium">Case Sensitive:</span>{' '}
305
+ {(selectedQualifierType.configuration as any).caseSensitive ? 'Yes' : 'No'}
306
+ </p>
307
+ )}
308
+ {(selectedQualifierType.configuration as any).enumeratedValues && (
309
+ <p>
310
+ <span className="font-medium">Allowed Values:</span>{' '}
311
+ {(selectedQualifierType.configuration as any).enumeratedValues.join(', ')}
312
+ </p>
313
+ )}
314
+ </>
315
+ )}
316
+ {selectedQualifierType.systemType === 'territory' && selectedQualifierType.configuration && (
317
+ <>
318
+ {(selectedQualifierType.configuration as any).acceptLowercase !== undefined && (
319
+ <p>
320
+ <span className="font-medium">Accept Lowercase:</span>{' '}
321
+ {(selectedQualifierType.configuration as any).acceptLowercase ? 'Yes' : 'No'}
322
+ </p>
323
+ )}
324
+ {(selectedQualifierType.configuration as any).allowedTerritories && (
325
+ <p>
326
+ <span className="font-medium">Allowed Territories:</span>{' '}
327
+ {(selectedQualifierType.configuration as any).allowedTerritories.join(', ')}
328
+ </p>
329
+ )}
330
+ </>
331
+ )}
332
+ </div>
333
+ </div>
334
+ )}
335
+ </div>
336
+
337
+ {/* Fixed Footer */}
338
+ <div className="flex justify-end space-x-3 px-6 py-4 border-t bg-gray-50 flex-shrink-0">
339
+ <button
340
+ onClick={onCancel}
341
+ className="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
342
+ >
343
+ Cancel
344
+ </button>
345
+ <button
346
+ onClick={handleSave}
347
+ className="px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
348
+ >
349
+ {qualifier ? 'Save Changes' : 'Add Qualifier'}
350
+ </button>
351
+ </div>
352
+ </div>
353
+ </div>
354
+ );
355
+ };