@fgv/ts-res-ui-components 5.0.0-21 → 5.0.0-23

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 (192) hide show
  1. package/README.md +401 -155
  2. package/config/jest.setup.js +10 -0
  3. package/dist/ts-res-ui-components.d.ts +1657 -76
  4. package/lib/components/common/QualifierContextControl.js +4 -1
  5. package/lib/components/common/ResourceTreeView.js +4 -1
  6. package/lib/components/forms/GenericQualifierTypeEditForm.d.ts +26 -0
  7. package/lib/components/forms/GenericQualifierTypeEditForm.js +166 -0
  8. package/lib/components/forms/QualifierEditForm.d.ts +1 -1
  9. package/lib/components/forms/index.d.ts +2 -0
  10. package/lib/components/forms/index.js +1 -0
  11. package/lib/components/orchestrator/ResourceOrchestrator.d.ts +3 -0
  12. package/lib/components/orchestrator/ResourceOrchestrator.js +118 -51
  13. package/lib/components/pickers/ResourcePicker/ResourcePickerTree.js +32 -10
  14. package/lib/components/pickers/ResourcePicker/index.js +4 -2
  15. package/lib/components/views/CompiledView/index.js +75 -16
  16. package/lib/components/views/ConfigurationView/index.js +94 -35
  17. package/lib/components/views/FilterView/index.js +7 -4
  18. package/lib/components/views/GridView/EditableGridCell.d.ts +76 -0
  19. package/lib/components/views/GridView/EditableGridCell.js +224 -0
  20. package/lib/components/views/GridView/GridSelector.d.ts +43 -0
  21. package/lib/components/views/GridView/GridSelector.js +89 -0
  22. package/lib/components/views/GridView/MultiGridView.d.ts +85 -0
  23. package/lib/components/views/GridView/MultiGridView.js +196 -0
  24. package/lib/components/views/GridView/ResourceGrid.d.ts +38 -0
  25. package/lib/components/views/GridView/ResourceGrid.js +232 -0
  26. package/lib/components/views/GridView/SharedContextControls.d.ts +47 -0
  27. package/lib/components/views/GridView/SharedContextControls.js +95 -0
  28. package/lib/components/views/GridView/cells/BooleanCell.d.ts +44 -0
  29. package/lib/components/views/GridView/cells/BooleanCell.js +49 -0
  30. package/lib/components/views/GridView/cells/DropdownCell.d.ts +58 -0
  31. package/lib/components/views/GridView/cells/DropdownCell.js +182 -0
  32. package/lib/components/views/GridView/cells/StringCell.d.ts +57 -0
  33. package/lib/components/views/GridView/cells/StringCell.js +106 -0
  34. package/lib/components/views/GridView/cells/TriStateCell.d.ts +54 -0
  35. package/lib/components/views/GridView/cells/TriStateCell.js +112 -0
  36. package/lib/components/views/GridView/cells/index.d.ts +15 -0
  37. package/lib/components/views/GridView/cells/index.js +11 -0
  38. package/lib/components/views/GridView/index.d.ts +53 -0
  39. package/lib/components/views/GridView/index.js +212 -0
  40. package/lib/components/views/ImportView/index.js +22 -19
  41. package/lib/components/views/MessagesWindow/index.js +4 -1
  42. package/lib/components/views/ResolutionView/index.js +8 -5
  43. package/lib/contexts/ObservabilityContext.d.ts +85 -0
  44. package/lib/contexts/ObservabilityContext.js +98 -0
  45. package/lib/contexts/index.d.ts +2 -0
  46. package/lib/contexts/index.js +24 -0
  47. package/lib/hooks/useConfigurationState.d.ts +3 -3
  48. package/lib/hooks/useResolutionState.js +850 -246
  49. package/lib/hooks/useResourceData.d.ts +7 -4
  50. package/lib/hooks/useResourceData.js +185 -184
  51. package/lib/index.d.ts +5 -1
  52. package/lib/index.js +8 -1
  53. package/lib/namespaces/GridTools.d.ts +136 -0
  54. package/lib/namespaces/GridTools.js +138 -0
  55. package/lib/namespaces/ObservabilityTools.d.ts +3 -0
  56. package/lib/namespaces/ObservabilityTools.js +23 -0
  57. package/lib/namespaces/ResolutionTools.d.ts +2 -1
  58. package/lib/namespaces/ResolutionTools.js +2 -0
  59. package/lib/namespaces/index.d.ts +2 -0
  60. package/lib/namespaces/index.js +2 -0
  61. package/lib/test/integration/observability.integration.test.d.ts +2 -0
  62. package/lib/test/unit/hooks/useResourceData.test.d.ts +2 -0
  63. package/lib/test/unit/utils/downloadHelper.test.d.ts +2 -0
  64. package/lib/test/unit/workflows/resolutionWorkflows.test.d.ts +2 -0
  65. package/lib/test/unit/workflows/resourceCreation.test.d.ts +2 -0
  66. package/lib/test/unit/workflows/resultPatternExtensions.test.d.ts +2 -0
  67. package/lib/test/unit/workflows/validation.test.d.ts +2 -0
  68. package/lib/types/index.d.ts +387 -20
  69. package/lib/types/index.js +2 -1
  70. package/lib/utils/cellValidation.d.ts +113 -0
  71. package/lib/utils/cellValidation.js +248 -0
  72. package/lib/utils/downloadHelper.d.ts +66 -0
  73. package/lib/utils/downloadHelper.js +195 -0
  74. package/lib/utils/observability/factories.d.ts +29 -0
  75. package/lib/utils/observability/factories.js +58 -0
  76. package/lib/utils/observability/implementations.d.ts +61 -0
  77. package/lib/utils/observability/implementations.js +103 -0
  78. package/lib/utils/observability/index.d.ts +4 -0
  79. package/lib/utils/observability/index.js +26 -0
  80. package/lib/utils/observability/interfaces.d.ts +30 -0
  81. package/lib/utils/observability/interfaces.js +23 -0
  82. package/lib/utils/resolutionEditing.js +2 -1
  83. package/lib/utils/resourceSelector.d.ts +97 -0
  84. package/lib/utils/resourceSelector.js +195 -0
  85. package/lib/utils/resourceSelectors.d.ts +146 -0
  86. package/lib/utils/resourceSelectors.js +233 -0
  87. package/lib/utils/tsResIntegration.d.ts +6 -41
  88. package/lib/utils/tsResIntegration.js +20 -16
  89. package/lib/utils/zipLoader/zipProcessingHelpers.d.ts +3 -2
  90. package/lib/utils/zipLoader/zipProcessingHelpers.js +6 -5
  91. package/lib-commonjs/components/common/QualifierContextControl.js +4 -1
  92. package/lib-commonjs/components/common/ResourceTreeView.js +4 -1
  93. package/lib-commonjs/components/forms/GenericQualifierTypeEditForm.js +171 -0
  94. package/lib-commonjs/components/forms/index.js +3 -1
  95. package/lib-commonjs/components/orchestrator/ResourceOrchestrator.js +118 -51
  96. package/lib-commonjs/components/pickers/ResourcePicker/ResourcePickerTree.js +32 -10
  97. package/lib-commonjs/components/pickers/ResourcePicker/index.js +4 -2
  98. package/lib-commonjs/components/views/CompiledView/index.js +75 -16
  99. package/lib-commonjs/components/views/ConfigurationView/index.js +93 -34
  100. package/lib-commonjs/components/views/FilterView/index.js +7 -4
  101. package/lib-commonjs/components/views/GridView/EditableGridCell.js +232 -0
  102. package/lib-commonjs/components/views/GridView/GridSelector.js +94 -0
  103. package/lib-commonjs/components/views/GridView/MultiGridView.js +201 -0
  104. package/lib-commonjs/components/views/GridView/ResourceGrid.js +237 -0
  105. package/lib-commonjs/components/views/GridView/SharedContextControls.js +100 -0
  106. package/lib-commonjs/components/views/GridView/cells/BooleanCell.js +54 -0
  107. package/lib-commonjs/components/views/GridView/cells/DropdownCell.js +187 -0
  108. package/lib-commonjs/components/views/GridView/cells/StringCell.js +111 -0
  109. package/lib-commonjs/components/views/GridView/cells/TriStateCell.js +117 -0
  110. package/lib-commonjs/components/views/GridView/cells/index.js +18 -0
  111. package/lib-commonjs/components/views/GridView/index.js +217 -0
  112. package/lib-commonjs/components/views/ImportView/index.js +22 -19
  113. package/lib-commonjs/components/views/MessagesWindow/index.js +4 -1
  114. package/lib-commonjs/components/views/ResolutionView/index.js +8 -5
  115. package/lib-commonjs/contexts/ObservabilityContext.js +104 -0
  116. package/lib-commonjs/contexts/index.js +30 -0
  117. package/lib-commonjs/hooks/useResolutionState.js +849 -245
  118. package/lib-commonjs/hooks/useResourceData.js +184 -215
  119. package/lib-commonjs/index.js +15 -1
  120. package/lib-commonjs/namespaces/GridTools.js +161 -0
  121. package/lib-commonjs/namespaces/ObservabilityTools.js +33 -0
  122. package/lib-commonjs/namespaces/ResolutionTools.js +10 -1
  123. package/lib-commonjs/namespaces/index.js +3 -1
  124. package/lib-commonjs/types/index.js +10 -0
  125. package/lib-commonjs/utils/cellValidation.js +253 -0
  126. package/lib-commonjs/utils/downloadHelper.js +198 -0
  127. package/lib-commonjs/utils/observability/factories.js +63 -0
  128. package/lib-commonjs/utils/observability/implementations.js +109 -0
  129. package/lib-commonjs/utils/observability/index.js +36 -0
  130. package/lib-commonjs/utils/observability/interfaces.js +24 -0
  131. package/lib-commonjs/utils/resolutionEditing.js +2 -1
  132. package/lib-commonjs/utils/resourceSelector.js +200 -0
  133. package/lib-commonjs/utils/resourceSelectors.js +242 -0
  134. package/lib-commonjs/utils/tsResIntegration.js +21 -16
  135. package/lib-commonjs/utils/zipLoader/zipProcessingHelpers.js +7 -5
  136. package/package.json +7 -7
  137. package/src/components/common/QualifierContextControl.tsx +0 -338
  138. package/src/components/common/ResolutionContextOptionsControl.tsx +0 -450
  139. package/src/components/common/ResolutionResults/index.tsx +0 -481
  140. package/src/components/common/ResourceListView.tsx +0 -167
  141. package/src/components/common/ResourcePickerOptionsControl.tsx +0 -351
  142. package/src/components/common/ResourceTreeView.tsx +0 -417
  143. package/src/components/common/SourceResourceDetail/index.tsx +0 -493
  144. package/src/components/forms/HierarchyEditor.tsx +0 -285
  145. package/src/components/forms/QualifierEditForm.tsx +0 -487
  146. package/src/components/forms/QualifierTypeEditForm.tsx +0 -458
  147. package/src/components/forms/ResourceTypeEditForm.tsx +0 -437
  148. package/src/components/forms/index.ts +0 -11
  149. package/src/components/orchestrator/ResourceOrchestrator.tsx +0 -444
  150. package/src/components/pickers/ResourcePicker/README.md +0 -570
  151. package/src/components/pickers/ResourcePicker/ResourceItem.tsx +0 -127
  152. package/src/components/pickers/ResourcePicker/ResourcePickerList.tsx +0 -114
  153. package/src/components/pickers/ResourcePicker/ResourcePickerTree.tsx +0 -461
  154. package/src/components/pickers/ResourcePicker/index.tsx +0 -234
  155. package/src/components/pickers/ResourcePicker/types.ts +0 -301
  156. package/src/components/pickers/ResourcePicker/utils/treeNavigation.ts +0 -210
  157. package/src/components/views/CompiledView/index.tsx +0 -1342
  158. package/src/components/views/ConfigurationView/index.tsx +0 -848
  159. package/src/components/views/FilterView/index.tsx +0 -681
  160. package/src/components/views/ImportView/index.tsx +0 -789
  161. package/src/components/views/MessagesWindow/index.tsx +0 -325
  162. package/src/components/views/ResolutionView/EditableJsonView.tsx +0 -386
  163. package/src/components/views/ResolutionView/NewResourceModal.tsx +0 -158
  164. package/src/components/views/ResolutionView/UnifiedChangeControls.tsx +0 -163
  165. package/src/components/views/ResolutionView/index.tsx +0 -751
  166. package/src/components/views/SourceView/index.tsx +0 -291
  167. package/src/hooks/useConfigurationState.ts +0 -436
  168. package/src/hooks/useFilterState.ts +0 -150
  169. package/src/hooks/useResolutionState.ts +0 -893
  170. package/src/hooks/useResourceData.ts +0 -596
  171. package/src/hooks/useViewState.ts +0 -97
  172. package/src/index.ts +0 -68
  173. package/src/namespaces/ConfigurationTools.ts +0 -59
  174. package/src/namespaces/FilterTools.ts +0 -47
  175. package/src/namespaces/ImportTools.ts +0 -42
  176. package/src/namespaces/PickerTools.ts +0 -104
  177. package/src/namespaces/ResolutionTools.ts +0 -68
  178. package/src/namespaces/ResourceTools.ts +0 -106
  179. package/src/namespaces/TsResTools.ts +0 -49
  180. package/src/namespaces/ViewStateTools.ts +0 -91
  181. package/src/namespaces/ZipTools.ts +0 -49
  182. package/src/namespaces/index.ts +0 -49
  183. package/src/types/index.ts +0 -1273
  184. package/src/utils/configurationUtils.ts +0 -339
  185. package/src/utils/fileProcessing.ts +0 -164
  186. package/src/utils/filterResources.ts +0 -356
  187. package/src/utils/resolutionEditing.ts +0 -346
  188. package/src/utils/resolutionUtils.ts +0 -740
  189. package/src/utils/tsResIntegration.ts +0 -475
  190. package/src/utils/zipLoader/index.ts +0 -5
  191. package/src/utils/zipLoader/zipProcessingHelpers.ts +0 -46
  192. package/src/utils/zipLoader/zipUtils.ts +0 -7
@@ -1,487 +0,0 @@
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
- import { Converters } from '@fgv/ts-utils';
5
-
6
- /**
7
- * Props for the QualifierEditForm component.
8
- *
9
- * @public
10
- */
11
- export interface QualifierEditFormProps {
12
- /** Existing qualifier to edit (undefined for creating new qualifier) */
13
- qualifier?: Qualifiers.IQualifierDecl;
14
- /** Available qualifier types for selection */
15
- qualifierTypes: QualifierTypes.Config.ISystemQualifierTypeConfig[];
16
- /** Callback fired when qualifier is saved */
17
- onSave: (qualifier: Qualifiers.IQualifierDecl) => void;
18
- /** Callback fired when editing is cancelled */
19
- onCancel: () => void;
20
- /** Names of existing qualifiers to prevent duplicates */
21
- existingNames?: string[];
22
- }
23
-
24
- interface FormData {
25
- name: string;
26
- typeName: string;
27
- defaultPriority: number;
28
- token: string;
29
- tokenIsOptional: boolean;
30
- defaultValue: string;
31
- }
32
-
33
- /**
34
- * Modal form component for creating and editing qualifiers in a ts-res system configuration.
35
- *
36
- * The QualifierEditForm provides a comprehensive interface for defining qualifiers that control
37
- * resource resolution behavior. It includes validation, type-specific configuration options,
38
- * and automatic token generation for streamlined qualifier creation.
39
- *
40
- * @example
41
- * ```tsx
42
- * import { ConfigurationTools } from '@fgv/ts-res-ui-components';
43
- *
44
- * // Creating a new language qualifier
45
- * const qualifierTypes = [
46
- * { name: 'language', systemType: 'language' },
47
- * { name: 'region', systemType: 'territory' }
48
- * ];
49
- *
50
- * const [showForm, setShowForm] = useState(false);
51
- * const [qualifiers, setQualifiers] = useState([]);
52
- *
53
- * const handleSave = (qualifier) => {
54
- * setQualifiers(prev => [...prev, qualifier]);
55
- * setShowForm(false);
56
- * };
57
- *
58
- * {showForm && (
59
- * <ConfigurationTools.QualifierEditForm
60
- * qualifierTypes={qualifierTypes}
61
- * onSave={handleSave}
62
- * onCancel={() => setShowForm(false)}
63
- * existingNames={qualifiers.map(q => q.name)}
64
- * />
65
- * )}
66
- * ```
67
- *
68
- * @example
69
- * ```tsx
70
- * // Editing an existing qualifier with validation
71
- * const existingQualifier = {
72
- * name: 'language',
73
- * typeName: 'language',
74
- * defaultPriority: 100,
75
- * token: 'lang',
76
- * tokenIsOptional: false,
77
- * defaultValue: 'en-US'
78
- * };
79
- *
80
- * <ConfigurationTools.QualifierEditForm
81
- * qualifier={existingQualifier}
82
- * qualifierTypes={availableTypes}
83
- * onSave={updateQualifier}
84
- * onCancel={closeEditor}
85
- * existingNames={otherQualifierNames}
86
- * />
87
- * ```
88
- *
89
- * @example
90
- * ```tsx
91
- * // Advanced qualifier configuration with enum values
92
- * const platformType = {
93
- * name: 'platform',
94
- * systemType: 'literal',
95
- * configuration: {
96
- * caseSensitive: false,
97
- * enumeratedValues: ['web', 'mobile', 'desktop'],
98
- * allowContextList: true
99
- * }
100
- * };
101
- *
102
- * const platformQualifier = {
103
- * name: 'platform',
104
- * typeName: 'platform',
105
- * defaultPriority: 80,
106
- * token: 'plat',
107
- * defaultValue: 'web,mobile' // Multiple values supported
108
- * };
109
- *
110
- * <ConfigurationTools.QualifierEditForm
111
- * qualifier={platformQualifier}
112
- * qualifierTypes={[platformType]}
113
- * onSave={handlePlatformSave}
114
- * onCancel={cancelEdit}
115
- * />
116
- * ```
117
- *
118
- * @public
119
- */
120
- export const QualifierEditForm: React.FC<QualifierEditFormProps> = ({
121
- qualifier,
122
- qualifierTypes,
123
- onSave,
124
- onCancel,
125
- existingNames = []
126
- }) => {
127
- const [formData, setFormData] = useState<FormData>(() => {
128
- if (qualifier) {
129
- return {
130
- name: qualifier.name,
131
- typeName: qualifier.typeName,
132
- defaultPriority: qualifier.defaultPriority,
133
- token: qualifier.token || '',
134
- tokenIsOptional: qualifier.tokenIsOptional || false,
135
- defaultValue: qualifier.defaultValue || ''
136
- };
137
- }
138
- return {
139
- name: '',
140
- typeName: qualifierTypes[0]?.name || '',
141
- defaultPriority: 50,
142
- token: '',
143
- tokenIsOptional: false,
144
- defaultValue: ''
145
- };
146
- });
147
-
148
- const [errors, setErrors] = useState<Record<string, string>>({});
149
-
150
- // Get the selected qualifier type for context
151
- const selectedQualifierType = qualifierTypes.find((qt) => qt.name === formData.typeName);
152
-
153
- // Type-safe extraction of allowContextList property
154
- const allowsContextList = (() => {
155
- if (!selectedQualifierType?.configuration) return false;
156
- const result = Converters.boolean.convert(
157
- (selectedQualifierType.configuration as Record<string, unknown>).allowContextList
158
- );
159
- return result.isSuccess() ? result.value : false;
160
- })();
161
-
162
- // Validation
163
- const validateForm = useCallback((): boolean => {
164
- const newErrors: Record<string, string> = {};
165
-
166
- if (!formData.name.trim()) {
167
- newErrors.name = 'Name is required';
168
- } else if (existingNames.includes(formData.name) && formData.name !== qualifier?.name) {
169
- newErrors.name = 'Name must be unique';
170
- }
171
-
172
- if (!formData.typeName) {
173
- newErrors.typeName = 'Qualifier type is required';
174
- }
175
-
176
- if (formData.defaultPriority < 0 || formData.defaultPriority > 1000) {
177
- newErrors.defaultPriority = 'Priority must be between 0 and 1000';
178
- }
179
-
180
- if (formData.token && !/^[a-zA-Z][a-zA-Z0-9_]*$/.test(formData.token)) {
181
- newErrors.token = 'Token must start with a letter and contain only letters, numbers, and underscores';
182
- }
183
-
184
- setErrors(newErrors);
185
- return Object.keys(newErrors).length === 0;
186
- }, [formData, existingNames, qualifier?.name]);
187
-
188
- const handleSave = useCallback(() => {
189
- if (!validateForm()) return;
190
-
191
- const result: Qualifiers.IQualifierDecl = {
192
- name: formData.name,
193
- typeName: formData.typeName,
194
- defaultPriority: formData.defaultPriority,
195
- ...(formData.token && { token: formData.token }),
196
- ...(formData.token && formData.tokenIsOptional && { tokenIsOptional: true }),
197
- ...(formData.defaultValue && { defaultValue: formData.defaultValue })
198
- };
199
-
200
- onSave(result);
201
- }, [formData, validateForm, onSave]);
202
-
203
- const updateField = useCallback(
204
- (field: keyof FormData, value: FormData[keyof FormData]) => {
205
- setFormData((prev) => {
206
- const updated = { ...prev, [field]: value };
207
-
208
- // Auto-generate token from name if no custom token is set
209
- if (field === 'name' && !prev.token) {
210
- updated.token = String(value)
211
- .toLowerCase()
212
- .replace(/[^a-zA-Z0-9]/g, '');
213
- }
214
-
215
- // Clear tokenIsOptional if token is cleared
216
- if (field === 'token' && !value) {
217
- updated.tokenIsOptional = false;
218
- }
219
-
220
- return updated;
221
- });
222
-
223
- if (errors[field]) {
224
- setErrors((prev) => ({ ...prev, [field]: '' }));
225
- }
226
- },
227
- [errors]
228
- );
229
-
230
- const getDefaultValuePlaceholder = (): string => {
231
- if (!selectedQualifierType) return 'Enter default value';
232
-
233
- switch (selectedQualifierType.systemType) {
234
- case 'language':
235
- return allowsContextList ? 'e.g., en-US or en-US,en' : 'e.g., en-US';
236
- case 'territory':
237
- return allowsContextList ? 'e.g., US or US,CA' : 'e.g., US';
238
- case 'literal':
239
- // Type-safe extraction of enumeratedValues
240
- const enumValues = (() => {
241
- if (!selectedQualifierType.configuration) return undefined;
242
- const result = Converters.arrayOf(Converters.string).convert(
243
- (selectedQualifierType.configuration as Record<string, unknown>).enumeratedValues
244
- );
245
- return result.isSuccess() ? result.value : undefined;
246
- })();
247
- if (enumValues && enumValues.length > 0) {
248
- return allowsContextList
249
- ? `e.g., ${enumValues[0]} or ${enumValues.slice(0, 2).join(',')}`
250
- : `e.g., ${enumValues[0]}`;
251
- }
252
- return allowsContextList ? 'e.g., value or value1,value2' : 'e.g., value';
253
- default:
254
- return 'Enter default value';
255
- }
256
- };
257
-
258
- return (
259
- <div className="fixed inset-0 bg-gray-600 bg-opacity-50 flex items-center justify-center z-50 p-4">
260
- <div className="bg-white rounded-lg shadow-xl max-w-2xl w-full h-full max-h-[calc(100vh-2rem)] flex flex-col">
261
- {/* Fixed Header */}
262
- <div className="flex items-center justify-between p-6 border-b flex-shrink-0">
263
- <h3 className="text-lg font-medium text-gray-900">
264
- {qualifier ? 'Edit Qualifier' : 'Add Qualifier'}
265
- </h3>
266
- <button onClick={onCancel} className="text-gray-400 hover:text-gray-600">
267
- <XMarkIcon className="w-6 h-6" />
268
- </button>
269
- </div>
270
-
271
- {/* Scrollable Content */}
272
- <div className="p-6 space-y-6 overflow-y-auto flex-1 min-h-0">
273
- {/* Basic Properties */}
274
- <div className="grid grid-cols-2 gap-4">
275
- <div>
276
- <label className="block text-sm font-medium text-gray-700 mb-1">Name *</label>
277
- <input
278
- type="text"
279
- value={formData.name}
280
- onChange={(e) => updateField('name', e.target.value)}
281
- className={`w-full px-3 py-2 border rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 ${
282
- errors.name ? 'border-red-300' : 'border-gray-300'
283
- }`}
284
- placeholder="Enter qualifier name"
285
- />
286
- {errors.name && <p className="mt-1 text-sm text-red-600">{errors.name}</p>}
287
- </div>
288
-
289
- <div>
290
- <label className="block text-sm font-medium text-gray-700 mb-1">Qualifier Type *</label>
291
- <select
292
- value={formData.typeName}
293
- onChange={(e) => updateField('typeName', e.target.value)}
294
- className={`w-full px-3 py-2 border rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 ${
295
- errors.typeName ? 'border-red-300' : 'border-gray-300'
296
- }`}
297
- >
298
- {qualifierTypes.length === 0 ? (
299
- <option value="">No qualifier types available</option>
300
- ) : (
301
- qualifierTypes.map((type) => (
302
- <option key={type.name} value={type.name}>
303
- {type.name} ({type.systemType})
304
- </option>
305
- ))
306
- )}
307
- </select>
308
- {errors.typeName && <p className="mt-1 text-sm text-red-600">{errors.typeName}</p>}
309
- </div>
310
- </div>
311
-
312
- <div className="grid grid-cols-2 gap-4">
313
- <div>
314
- <label className="block text-sm font-medium text-gray-700 mb-1">Default Priority *</label>
315
- <input
316
- type="number"
317
- min="0"
318
- max="1000"
319
- value={formData.defaultPriority}
320
- onChange={(e) => updateField('defaultPriority', parseInt(e.target.value) || 0)}
321
- className={`w-full px-3 py-2 border rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 ${
322
- errors.defaultPriority ? 'border-red-300' : 'border-gray-300'
323
- }`}
324
- placeholder="50"
325
- />
326
- {errors.defaultPriority && (
327
- <p className="mt-1 text-sm text-red-600">{errors.defaultPriority}</p>
328
- )}
329
- <p className="mt-1 text-xs text-gray-500">Higher numbers have higher priority (0-1000)</p>
330
- </div>
331
-
332
- <div>
333
- <label className="block text-sm font-medium text-gray-700 mb-1">
334
- Token
335
- <span className="ml-1 text-gray-500">(optional)</span>
336
- </label>
337
- <input
338
- type="text"
339
- value={formData.token}
340
- onChange={(e) => updateField('token', e.target.value)}
341
- className={`w-full px-3 py-2 border rounded-md shadow-sm focus:outline-none focus:ring-1 focus:ring-blue-500 ${
342
- errors.token ? 'border-red-300' : 'border-gray-300'
343
- }`}
344
- placeholder="e.g., lang, locale"
345
- />
346
- {errors.token && <p className="mt-1 text-sm text-red-600">{errors.token}</p>}
347
- <p className="mt-1 text-xs text-gray-500">Used to identify this qualifier in resource names</p>
348
- </div>
349
- </div>
350
-
351
- {/* Token Options */}
352
- {formData.token && (
353
- <div className="flex items-center">
354
- <input
355
- type="checkbox"
356
- id="tokenIsOptional"
357
- checked={formData.tokenIsOptional}
358
- onChange={(e) => updateField('tokenIsOptional', e.target.checked)}
359
- className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
360
- />
361
- <label htmlFor="tokenIsOptional" className="ml-2 text-sm text-gray-700">
362
- Token is optional in resource names
363
- </label>
364
- <div className="ml-2 group relative">
365
- <InformationCircleIcon className="w-4 h-4 text-gray-400" />
366
- <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">
367
- Allow resources without this qualifier token
368
- </div>
369
- </div>
370
- </div>
371
- )}
372
-
373
- {/* Default Value */}
374
- <div>
375
- <label className="block text-sm font-medium text-gray-700 mb-1">
376
- Default Value
377
- <span className="ml-1 text-gray-500">(optional)</span>
378
- </label>
379
- <input
380
- type="text"
381
- value={formData.defaultValue}
382
- onChange={(e) => updateField('defaultValue', e.target.value)}
383
- 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"
384
- placeholder={getDefaultValuePlaceholder()}
385
- />
386
- <div className="mt-1 text-xs text-gray-500">
387
- {selectedQualifierType && (
388
- <div>
389
- <p>
390
- Qualifier type: <span className="font-medium">{selectedQualifierType.systemType}</span>
391
- </p>
392
- {allowsContextList && (
393
- <p className="text-blue-600">
394
- This qualifier type supports multiple values (comma-separated)
395
- </p>
396
- )}
397
- </div>
398
- )}
399
- </div>
400
- </div>
401
-
402
- {/* Qualifier Type Information */}
403
- {selectedQualifierType && (
404
- <div className="p-4 bg-gray-50 rounded-lg">
405
- <h4 className="font-medium text-gray-900 mb-2">Qualifier Type Information</h4>
406
- <div className="text-sm text-gray-600 space-y-1">
407
- <p>
408
- <span className="font-medium">System Type:</span> {selectedQualifierType.systemType}
409
- </p>
410
- <p>
411
- <span className="font-medium">Supports Context List:</span>{' '}
412
- {allowsContextList ? 'Yes' : 'No'}
413
- </p>
414
- {selectedQualifierType.systemType === 'literal' && selectedQualifierType.configuration && (
415
- <React.Fragment>
416
- {(selectedQualifierType.configuration as Record<string, unknown>).caseSensitive !==
417
- undefined && (
418
- <p>
419
- <span className="font-medium">Case Sensitive:</span>{' '}
420
- {((selectedQualifierType.configuration as Record<string, unknown>)
421
- .caseSensitive as boolean)
422
- ? 'Yes'
423
- : 'No'}
424
- </p>
425
- )}
426
- {((selectedQualifierType.configuration as Record<string, unknown>).enumeratedValues as
427
- | string[]
428
- | undefined) && (
429
- <p>
430
- <span className="font-medium">Allowed Values:</span>{' '}
431
- {(
432
- (selectedQualifierType.configuration as Record<string, unknown>)
433
- .enumeratedValues as string[]
434
- ).join(', ')}
435
- </p>
436
- )}
437
- </React.Fragment>
438
- )}
439
- {selectedQualifierType.systemType === 'territory' && selectedQualifierType.configuration && (
440
- <React.Fragment>
441
- {(selectedQualifierType.configuration as Record<string, unknown>).acceptLowercase !==
442
- undefined && (
443
- <p>
444
- <span className="font-medium">Accept Lowercase:</span>{' '}
445
- {((selectedQualifierType.configuration as Record<string, unknown>)
446
- .acceptLowercase as boolean)
447
- ? 'Yes'
448
- : 'No'}
449
- </p>
450
- )}
451
- {((selectedQualifierType.configuration as Record<string, unknown>).allowedTerritories as
452
- | string[]
453
- | undefined) && (
454
- <p>
455
- <span className="font-medium">Allowed Territories:</span>{' '}
456
- {(
457
- (selectedQualifierType.configuration as Record<string, unknown>)
458
- .allowedTerritories as string[]
459
- ).join(', ')}
460
- </p>
461
- )}
462
- </React.Fragment>
463
- )}
464
- </div>
465
- </div>
466
- )}
467
- </div>
468
-
469
- {/* Fixed Footer */}
470
- <div className="flex justify-end space-x-3 px-6 py-4 border-t bg-gray-50 flex-shrink-0">
471
- <button
472
- onClick={onCancel}
473
- 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"
474
- >
475
- Cancel
476
- </button>
477
- <button
478
- onClick={handleSave}
479
- 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"
480
- >
481
- {qualifier ? 'Save Changes' : 'Add Qualifier'}
482
- </button>
483
- </div>
484
- </div>
485
- </div>
486
- );
487
- };