@balena/ui-shared-components 14.0.3 → 14.1.0-build-limit-field-lengths-2d063da12c2b1e3b691fc126191269d24d81aae1-1

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.
@@ -8,6 +8,7 @@ import { useQuery } from '@tanstack/react-query';
8
8
  import { Spinner } from '../../Spinner';
9
9
  import { TagManagementDialog } from '../../TagManagementDialog';
10
10
  export const Tags = ({ selected, rjstContext, schema, setIsBusyMessage, refresh, onDone, }) => {
11
+ var _a;
11
12
  const { t } = useTranslation();
12
13
  const { sdk, internalPineFilter, checkedState } = rjstContext;
13
14
  const getAllTags = (sdk === null || sdk === void 0 ? void 0 : sdk.tags) && 'getAll' in sdk.tags ? sdk.tags.getAll : null;
@@ -81,7 +82,10 @@ export const Tags = ({ selected, rjstContext, schema, setIsBusyMessage, refresh,
81
82
  if (!rjstContext.tagField || !rjstContext.nameField || !items) {
82
83
  return null;
83
84
  }
84
- return (_jsx(Spinner, { show: isPending, sx: { width: '100%', height: '100%' }, children: _jsx(TagManagementDialog, { items: items, itemType: rjstContext.resource, titleField: getItemName !== null && getItemName !== void 0 ? getItemName : rjstContext.nameField, tagField: rjstContext.tagField, done: async (tagSubmitInfo) => {
85
+ const tagSchema = (_a = schema.properties) === null || _a === void 0 ? void 0 : _a[rjstContext.tagField];
86
+ return (_jsx(Spinner, { show: isPending, sx: { width: '100%', height: '100%' }, children: _jsx(TagManagementDialog, { items: items, itemType: rjstContext.resource, titleField: getItemName !== null && getItemName !== void 0 ? getItemName : rjstContext.nameField, tagField: rjstContext.tagField, tagSchema: tagSchema != null && typeof tagSchema === 'object'
87
+ ? tagSchema
88
+ : undefined, done: async (tagSubmitInfo) => {
85
89
  await changeTags(tagSubmitInfo);
86
90
  onDone();
87
91
  }, cancel: () => {
@@ -1,11 +1,16 @@
1
1
  import type { ResourceTagInfo } from './models';
2
- import type { TFunction } from '../../hooks/useTranslations';
2
+ import type { JSONSchema7 as JSONSchema } from 'json-schema';
3
3
  interface AddTagFormProps<T> {
4
- t: TFunction;
5
4
  itemType: string;
5
+ /**
6
+ * This is atm only used for constraint validation,
7
+ * but in the future it would be great if this becomes mandatory
8
+ * and we use an autogenerated form.
9
+ */
10
+ schema?: JSONSchema;
6
11
  existingTags: Array<ResourceTagInfo<T>>;
7
12
  overridableTags?: Array<ResourceTagInfo<T>>;
8
13
  addTag: (tag: ResourceTagInfo<T>) => void;
9
14
  }
10
- export declare const AddTagForm: <T extends object>({ itemType, existingTags, overridableTags, addTag, }: AddTagFormProps<T>) => import("react/jsx-runtime").JSX.Element;
15
+ export declare const AddTagForm: <T extends object>({ itemType, schema, existingTags, overridableTags, addTag, }: AddTagFormProps<T>) => import("react/jsx-runtime").JSX.Element;
11
16
  export {};
@@ -10,33 +10,63 @@ import { stopKeyDownEvent, withPreventDefault, } from '../../utils/eventHandling
10
10
  import { SimpleConfirmationDialog, } from '../SimpleConfirmationDialog';
11
11
  import { useRandomUUID } from '../../hooks/useRandomUUID';
12
12
  const RESERVED_NAMESPACES = ['io.resin.', 'io.balena.'];
13
- const newTagValidationRules = (t, key, existingTags) => {
13
+ const newTagValidationRules = (t, schema, existingTags, key, value) => {
14
+ var _a, _b;
15
+ const tagKeySchema = (_a = schema === null || schema === void 0 ? void 0 : schema.properties) === null || _a === void 0 ? void 0 : _a.tag_key;
16
+ const tagValueSchema = (_b = schema === null || schema === void 0 ? void 0 : schema.properties) === null || _b === void 0 ? void 0 : _b.value;
17
+ const tagKeyMaxLength = tagKeySchema != null && typeof tagKeySchema === 'object'
18
+ ? tagKeySchema.maxLength
19
+ : null;
20
+ const tagValueMaxLength = tagValueSchema != null && typeof tagValueSchema === 'object'
21
+ ? tagValueSchema.maxLength
22
+ : null;
14
23
  return [
15
24
  {
16
25
  test: () => !key || isEmpty(key),
26
+ field: 'tag_key',
17
27
  message: t('fields_errors.tag_name_cannot_be_empty'),
18
28
  },
19
29
  {
20
30
  test: () => /\s/.test(key),
31
+ field: 'tag_key',
21
32
  message: t('fields_errors.tag_names_cannot_contain_whitespace'),
22
33
  },
23
34
  {
24
35
  test: () => RESERVED_NAMESPACES.some((reserved) => startsWith(key, reserved)),
36
+ field: 'tag_key',
25
37
  message: t(`fields_errors.some_tag_keys_are_reserved`, {
26
38
  namespace: RESERVED_NAMESPACES.join(', '),
27
39
  }),
28
40
  },
29
41
  {
30
42
  test: () => existingTags.some((tag) => tag.state !== 'deleted' && tag.tag_key === key),
43
+ field: 'tag_key',
31
44
  message: t('fields_errors.tag_with_same_name_exists'),
32
45
  },
46
+ ...(tagKeyMaxLength != null
47
+ ? [
48
+ {
49
+ test: () => key.length > tagKeyMaxLength,
50
+ field: 'tag_key',
51
+ message: t('fields_errors.tag_name_cannot_longer_than_maximum_characters', { maximum: tagKeyMaxLength }),
52
+ },
53
+ ]
54
+ : []),
55
+ ...(tagValueMaxLength != null
56
+ ? [
57
+ {
58
+ test: () => value.length > tagValueMaxLength,
59
+ field: 'value',
60
+ message: t('fields_errors.tag_value_cannot_longer_than_maximum_characters', { maximum: tagValueMaxLength }),
61
+ },
62
+ ]
63
+ : []),
33
64
  ];
34
65
  };
35
- export const AddTagForm = ({ itemType, existingTags, overridableTags = [], addTag, }) => {
66
+ export const AddTagForm = ({ itemType, schema, existingTags, overridableTags = [], addTag, }) => {
36
67
  const { t } = useTranslation();
37
68
  const [tagKey, setTagKey] = React.useState('');
38
69
  const [value, setValue] = React.useState('');
39
- const [tagKeyIsInvalid, setTagKeyIsInvalid] = React.useState(false);
40
70
  const [error, setError] = React.useState();
41
71
  const [canSubmit, setCanSubmit] = React.useState(false);
42
72
  const [confirmationDialogOptions, setConfirmationDialogOptions] = React.useState();
@@ -45,9 +75,8 @@ export const AddTagForm = ({ itemType, existingTags, overridableTags = [], addTa
45
75
  const formId = useRandomUUID();
46
76
  const formUuid = `add-tag-form-${formId}`;
47
77
  const checkNewTagValidity = (key) => {
48
- const failedRule = newTagValidationRules(t, key, existingTags).find((rule) => rule.test());
78
+ const failedRule = newTagValidationRules(t, schema, existingTags, key, value).find((rule) => rule.test());
49
79
  const hasErrors = !!failedRule;
50
- setTagKeyIsInvalid(hasErrors);
51
80
  setError(failedRule);
52
81
  setCanSubmit(!hasErrors);
53
82
  return hasErrors;
@@ -92,7 +121,6 @@ export const AddTagForm = ({ itemType, existingTags, overridableTags = [], addTa
92
121
  });
93
122
  setTagKey('');
94
123
  setValue('');
95
- setTagKeyIsInvalid(false);
96
124
  setError(undefined);
97
125
  setCanSubmit(false);
98
126
  if (tagKeyInput === null || tagKeyInput === void 0 ? void 0 : tagKeyInput.current) {
@@ -112,13 +140,13 @@ export const AddTagForm = ({ itemType, existingTags, overridableTags = [], addTa
112
140
  }, fullWidth: true, ref: tagKeyInput, onChange: (e) => {
113
141
  setTagKey(e.target.value);
114
142
  checkNewTagValidity(e.target.value);
115
- }, value: tagKey, error: tagKeyIsInvalid, placeholder: t('labels.tag_name') }), _jsx(TextField, { slotProps: {
143
+ }, value: tagKey, error: (error === null || error === void 0 ? void 0 : error.field) === 'tag_key', placeholder: t('labels.tag_name') }), _jsx(TextField, { slotProps: {
116
144
  htmlInput: {
117
145
  form: formUuid,
118
146
  },
119
147
  }, fullWidth: true, ref: valueInput, onChange: (e) => {
120
148
  setValue(e.target.value);
121
- }, value: value, placeholder: t('labels.value') }), _jsx("form", { id: formUuid, onSubmit: internalAddTag, children: _jsx(Button, { sx: {
149
+ }, value: value, error: (error === null || error === void 0 ? void 0 : error.field) === 'value', placeholder: t('labels.value') }), _jsx("form", { id: formUuid, onSubmit: internalAddTag, children: _jsx(Button, { sx: {
122
150
  width: 120,
123
151
  }, onClick: internalAddTag, disabled: !canSubmit, children: t('actions.add_tag') }) }), confirmationDialogOptions && (_jsx(SimpleConfirmationDialog, Object.assign({}, confirmationDialogOptions)))] }), error && _jsx(Callout, { severity: "danger", children: error.message })] }));
124
152
  };
@@ -1,3 +1,4 @@
1
+ import type { JSONSchema7 as JSONSchema } from 'json-schema';
1
2
  import type { ResourceTagSubmitInfo, SubmitInfo, TaggedResource } from './models';
2
3
  export interface TagManagementDialogProps<T> {
3
4
  /** Selected items to tag */
@@ -8,9 +9,11 @@ export interface TagManagementDialogProps<T> {
8
9
  titleField: keyof T | ((item: T) => string);
9
10
  /** Tags property in the selected item */
10
11
  tagField: keyof T;
12
+ /** The schema of the tag resource */
13
+ tagSchema?: JSONSchema;
11
14
  /** On cancel press event */
12
15
  cancel: () => void;
13
16
  /** On done press event */
14
17
  done: (tagSubmitInfo: SubmitInfo<ResourceTagSubmitInfo, ResourceTagSubmitInfo>) => void;
15
18
  }
16
- export declare const TagManagementDialog: <T extends TaggedResource>({ items, itemType, titleField, tagField, cancel, done, }: TagManagementDialogProps<T>) => import("react/jsx-runtime").JSX.Element | null;
19
+ export declare const TagManagementDialog: <T extends TaggedResource>({ items, itemType, titleField, tagField, tagSchema, cancel, done, }: TagManagementDialogProps<T>) => import("react/jsx-runtime").JSX.Element | null;
@@ -98,11 +98,16 @@ const PreviousTagProperty = styled(TagProperty) `
98
98
  }
99
99
  }
100
100
  `;
101
- export const TagManagementDialog = ({ items, itemType, titleField, tagField, cancel, done, }) => {
101
+ export const TagManagementDialog = ({ items, itemType, titleField, tagField, tagSchema, cancel, done, }) => {
102
+ var _a;
102
103
  const { t } = useTranslation();
103
104
  const [editingTag, setEditingTag] = React.useState();
104
105
  const [tags, setTags] = React.useState();
105
106
  const [partialTags, setPartialTags] = React.useState();
107
+ const tagValueSchema = (_a = tagSchema === null || tagSchema === void 0 ? void 0 : tagSchema.properties) === null || _a === void 0 ? void 0 : _a.value;
108
+ const tagValueMaxLength = tagValueSchema != null && typeof tagValueSchema === 'object'
109
+ ? tagValueSchema.maxLength
110
+ : null;
106
111
  const tagDiffs = React.useMemo(() => getResourceTagSubmitInfo(tags !== null && tags !== void 0 ? tags : []), [tags]);
107
112
  React.useEffect(() => {
108
113
  const allTags = groupResourcesByTags(items, tagField);
@@ -191,7 +196,7 @@ export const TagManagementDialog = ({ items, itemType, titleField, tagField, can
191
196
  : toString(item[titleField]);
192
197
  return title || `(${t('no_data.no_name_set')})`;
193
198
  };
194
- return (_jsxs(DialogWithCloseButton, { open: true, title: _jsxs(Stack, { children: [_jsxs(Typography, { variant: "h3", mt: 0, mb: 10, children: [items.length > 1 && _jsxs("span", { children: [t('labels.shared'), " "] }), t('labels.tags')] }), _jsx(CollectionSummary, { items: items.map(getItemTitle).sort(), itemsType: t('resource.' + itemType, { count: items.length }), maxVisibleItemCount: 10 })] }), children: [_jsxs(DialogContent, { children: [_jsx(AddTagForm, { itemType: itemType, existingTags: tags, overridableTags: partialTags, addTag: addTag, t: t }), _jsxs(Table, { children: [_jsxs(TableHead, { children: [_jsx(TableCell, {}), _jsx(TableCell, { children: t('labels.tag_name') }), _jsx(TableCell, { children: t('labels.value') }), _jsx(TableCell, {})] }), _jsx(TableBody, { children: !tags.length ? (_jsxs(TableRow, { children: [_jsx(TableCell, {}), _jsx(TableCell, { children: t(`errors.no_tags_for_selected_itemtype`, {
199
+ return (_jsxs(DialogWithCloseButton, { open: true, title: _jsxs(Stack, { children: [_jsxs(Typography, { variant: "h3", mt: 0, mb: 10, children: [items.length > 1 && _jsxs("span", { children: [t('labels.shared'), " "] }), t('labels.tags')] }), _jsx(CollectionSummary, { items: items.map(getItemTitle).sort(), itemsType: t('resource.' + itemType, { count: items.length }), maxVisibleItemCount: 10 })] }), children: [_jsxs(DialogContent, { children: [_jsx(AddTagForm, { itemType: itemType, schema: tagSchema, existingTags: tags, overridableTags: partialTags, addTag: addTag }), _jsxs(Table, { children: [_jsxs(TableHead, { children: [_jsx(TableCell, {}), _jsx(TableCell, { children: t('labels.tag_name') }), _jsx(TableCell, { children: t('labels.value') }), _jsx(TableCell, {})] }), _jsx(TableBody, { children: !tags.length ? (_jsxs(TableRow, { children: [_jsx(TableCell, {}), _jsx(TableCell, { children: t(`errors.no_tags_for_selected_itemtype`, {
195
200
  count: items.length,
196
201
  itemType,
197
202
  }) })] })) : (tags.map((tag) => {
@@ -216,7 +221,9 @@ export const TagManagementDialog = ({ items, itemType, titleField, tagField, can
216
221
  setEditingTagValue(e.target.value);
217
222
  }, onBlur: () => {
218
223
  endTagEdit();
219
- }, value: editingTag.value, placeholder: t('labels.tag_value') }))] }), _jsx(TableCell, { children: tag.state && (_jsx(Button, { variant: "text", startIcon: _jsx(FontAwesomeIcon, { icon: faUndo }), onClick: () => {
224
+ }, value: editingTag.value, placeholder: t('labels.tag_value'), inputProps: tagValueMaxLength != null
225
+ ? { maxLength: tagValueMaxLength }
226
+ : undefined }))] }), _jsx(TableCell, { children: tag.state && (_jsx(Button, { variant: "text", startIcon: _jsx(FontAwesomeIcon, { icon: faUndo }), onClick: () => {
220
227
  undoTagChanges(tag);
221
228
  }, sx: {
222
229
  width: UndoButtonMinWidth,
@@ -31,6 +31,8 @@ const translationMap = {
31
31
  'errors.no_tags_for_selected_itemtype_plural': 'The selected {{itemType}}s have no tags in common',
32
32
  'fields_errors.tag_name_cannot_be_empty': "The tag name can't be empty.",
33
33
  'fields_errors.tag_names_cannot_contain_whitespace': 'Tag names cannot contain whitespace',
34
+ 'fields_errors.tag_name_cannot_longer_than_maximum_characters': `The tag name can't be longer than {{maximum}} characters.`,
35
+ 'fields_errors.tag_value_cannot_longer_than_maximum_characters': `The tag value can't be longer than {{maximum}} characters.`,
34
36
  'fields_errors.some_tag_keys_are_reserved': 'Tag names beginning with {{namespace}} are reserved',
35
37
  'fields_errors.tag_with_same_name_exists': 'A tag with the same name already exists',
36
38
  'fields_errors.does_not_satisfy_minimum': 'Must be greater than or equal to {{minimum}}',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@balena/ui-shared-components",
3
- "version": "14.0.3",
3
+ "version": "14.1.0-build-limit-field-lengths-2d063da12c2b1e3b691fc126191269d24d81aae1-1",
4
4
  "main": "./dist/index.js",
5
5
  "sideEffects": false,
6
6
  "files": [
@@ -132,7 +132,7 @@
132
132
  },
133
133
  "homepage": "https://github.com/balena-io/ui-shared-components#readme",
134
134
  "versionist": {
135
- "publishedAt": "2025-09-15T12:00:29.168Z"
135
+ "publishedAt": "2025-09-15T12:30:23.623Z"
136
136
  },
137
137
  "overrides": {
138
138
  "storybook": "$storybook"