@axinom/mosaic-ui 0.63.0-rc.1 → 0.63.0-rc.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 (60) hide show
  1. package/dist/components/Accordion/Accordion.d.ts.map +1 -1
  2. package/dist/components/Explorer/BulkEdit/FormFieldsConfigConverter.d.ts +1 -1
  3. package/dist/components/Explorer/BulkEdit/FormFieldsConfigConverter.d.ts.map +1 -1
  4. package/dist/components/Explorer/BulkEdit/useBulkEdit.d.ts.map +1 -1
  5. package/dist/components/Explorer/Explorer.d.ts.map +1 -1
  6. package/dist/components/Explorer/Explorer.model.d.ts +1 -1
  7. package/dist/components/Explorer/Explorer.model.d.ts.map +1 -1
  8. package/dist/components/FieldSelection/FieldSelection.d.ts +2 -0
  9. package/dist/components/FieldSelection/FieldSelection.d.ts.map +1 -1
  10. package/dist/components/FormElements/DateTimeField/DateTimeText.d.ts +1 -1
  11. package/dist/components/FormElements/DateTimeField/DateTimeText.d.ts.map +1 -1
  12. package/dist/components/FormElements/DateTimeField/DateTimeTextField.d.ts.map +1 -1
  13. package/dist/components/FormStation/FormStation.d.ts +12 -1
  14. package/dist/components/FormStation/FormStation.d.ts.map +1 -1
  15. package/dist/components/FormStation/FormStationHeader/FormStationHeader.d.ts +3 -0
  16. package/dist/components/FormStation/FormStationHeader/FormStationHeader.d.ts.map +1 -1
  17. package/dist/components/FormStation/helpers/useDataProvider.d.ts +1 -1
  18. package/dist/components/FormStation/helpers/useDataProvider.d.ts.map +1 -1
  19. package/dist/components/Icons/Icons.d.ts.map +1 -1
  20. package/dist/components/Icons/Icons.models.d.ts +2 -1
  21. package/dist/components/Icons/Icons.models.d.ts.map +1 -1
  22. package/dist/components/PageHeader/PageHeaderAction/PageHeaderAction.d.ts.map +1 -1
  23. package/dist/components/PageHeader/PageHeaderAction/PageHeaderAction.model.d.ts +2 -0
  24. package/dist/components/PageHeader/PageHeaderAction/PageHeaderAction.model.d.ts.map +1 -1
  25. package/dist/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.d.ts.map +1 -1
  26. package/dist/index.es.js +4 -4
  27. package/dist/index.es.js.map +1 -1
  28. package/dist/index.js +4 -4
  29. package/dist/index.js.map +1 -1
  30. package/package.json +2 -2
  31. package/src/components/Accordion/Accordion.scss +14 -4
  32. package/src/components/Accordion/Accordion.spec.tsx +44 -64
  33. package/src/components/Accordion/Accordion.stories.tsx +8 -0
  34. package/src/components/Accordion/Accordion.tsx +9 -3
  35. package/src/components/Accordion/AccordionItem/AccordionItem.spec.tsx +46 -84
  36. package/src/components/Explorer/BulkEdit/FormFieldsConfigConverter.spec.tsx +22 -18
  37. package/src/components/Explorer/BulkEdit/FormFieldsConfigConverter.tsx +94 -20
  38. package/src/components/Explorer/BulkEdit/useBulkEdit.tsx +6 -8
  39. package/src/components/Explorer/Explorer.model.ts +1 -1
  40. package/src/components/Explorer/Explorer.tsx +1 -0
  41. package/src/components/Explorer/helpers/useActions.ts +1 -1
  42. package/src/components/FieldSelection/FieldSelection.scss +7 -0
  43. package/src/components/FieldSelection/FieldSelection.spec.tsx +0 -2
  44. package/src/components/FieldSelection/FieldSelection.tsx +13 -11
  45. package/src/components/FormElements/DateTimeField/DateTimeText.tsx +8 -14
  46. package/src/components/FormElements/DateTimeField/DateTimeTextField.tsx +18 -5
  47. package/src/components/FormStation/Create/Create.stories.tsx +9 -0
  48. package/src/components/FormStation/FormStation.stories.tsx +2 -2
  49. package/src/components/FormStation/FormStation.tsx +16 -1
  50. package/src/components/FormStation/FormStationHeader/FormStationHeader.tsx +20 -3
  51. package/src/components/FormStation/helpers/useDataProvider.ts +6 -2
  52. package/src/components/Icons/Icons.models.ts +1 -0
  53. package/src/components/Icons/Icons.tsx +17 -0
  54. package/src/components/PageHeader/PageHeader.stories.tsx +8 -0
  55. package/src/components/PageHeader/PageHeaderAction/PageHeaderAction.model.ts +2 -0
  56. package/src/components/PageHeader/PageHeaderAction/PageHeaderAction.scss +28 -0
  57. package/src/components/PageHeader/PageHeaderAction/PageHeaderAction.spec.tsx +0 -10
  58. package/src/components/PageHeader/PageHeaderAction/PageHeaderAction.tsx +4 -3
  59. package/src/components/PageHeader/PageHeaderActionsGroup/PageHeaderActionsGroup.tsx +5 -1
  60. package/src/styles/variables.scss +3 -0
@@ -6,7 +6,6 @@ import React, {
6
6
  useState,
7
7
  } from 'react';
8
8
  import { Data } from '../../../types';
9
- import { FieldSelection } from '../../FieldSelection';
10
9
  import { FormStation } from '../../FormStation';
11
10
  import { IconName } from '../../Icons';
12
11
  import {
@@ -74,8 +73,9 @@ export const useBulkEdit = <T extends Data>({
74
73
  () =>
75
74
  bulkEditRegistration
76
75
  ? {
77
- label: bulkEditRegistration.label,
76
+ label: bulkEditRegistration.label ?? 'Bulk Edit',
78
77
  icon: bulkEditRegistration.icon ?? IconName.BulkEdit,
78
+ tag: 'BETA',
79
79
  onClick: () => setIsBulkEditMode((prev) => !prev),
80
80
  }
81
81
  : undefined,
@@ -93,12 +93,8 @@ export const useBulkEdit = <T extends Data>({
93
93
  if (bulkEditRegistration?.component) {
94
94
  return bulkEditRegistration.component;
95
95
  } else if (bulkEditRegistration?.config) {
96
- return (
97
- <FieldSelection>
98
- {BulkEditFormFieldsConfigConverter(
99
- bulkEditRegistration?.config.fields,
100
- )}
101
- </FieldSelection>
96
+ return BulkEditFormFieldsConfigConverter(
97
+ bulkEditRegistration?.config.fields,
102
98
  );
103
99
  }
104
100
  return null;
@@ -116,6 +112,8 @@ export const useBulkEdit = <T extends Data>({
116
112
  : undefined
117
113
  }
118
114
  showSaveHeaderAction={!noItemsSelected}
115
+ saveHeaderActionConfig={{ label: 'Apply', icon: IconName.Checkmark }}
116
+ saveNotificationMessage="Your changes are being applied to the selected items."
119
117
  >
120
118
  {BulkEditContent}
121
119
  </FormStation>
@@ -127,7 +127,7 @@ export interface QuickEditRegistration<T> {
127
127
 
128
128
  export interface BulkEditRegistration<T extends Data> {
129
129
  /** The label of the action. */
130
- label: string;
130
+ label?: string;
131
131
  /** Optional built in icon. This prop also accepts an img src. */
132
132
  icon?: IconName | string;
133
133
  /** Component to render. This will override the component that is generated. */
@@ -487,6 +487,7 @@ export const Explorer = React.forwardRef(function Explorer<T extends Data>(
487
487
  onItemSelected={itemSelectedHandler}
488
488
  onRequestMoreData={onRequestMoreData}
489
489
  onSortChanged={(sortData: SortData<T>) => {
490
+ listRef.current?.resetSelection();
490
491
  onSortChanged(sortData);
491
492
  setSortOrder(sortData);
492
493
  }}
@@ -63,6 +63,7 @@ export const useActions = <T extends Data>({
63
63
  }: UseActionsProps<T>): UseActionsReturnType => {
64
64
  const bulkActionItems: PageHeaderJsActionProps[] = useMemo(
65
65
  () => [
66
+ ...(bulkEditAction ? [bulkEditAction] : []),
66
67
  ...(bulkActions ?? []).map((action) => ({
67
68
  ...action,
68
69
  onClick: async () => {
@@ -101,7 +102,6 @@ export const useActions = <T extends Data>({
101
102
  }
102
103
  },
103
104
  })),
104
- ...(bulkEditAction ? [bulkEditAction] : []),
105
105
  ],
106
106
  [
107
107
  bulkActions,
@@ -5,6 +5,13 @@
5
5
  grid-template-columns: 1fr 50px;
6
6
  gap: 10px;
7
7
  align-items: center;
8
+ }
9
+
10
+ .selectionHeader {
11
+ display: grid;
12
+ grid-template-columns: 1fr;
13
+ gap: 10px;
14
+ align-items: center;
8
15
 
9
16
  div:has(> input) {
10
17
  padding: 2px;
@@ -34,7 +34,6 @@ describe('FieldSelection', () => {
34
34
  target: { value: 'field1' },
35
35
  });
36
36
  fireEvent.click(screen.getByText('Field 1'));
37
- fireEvent.click(screen.getByTestId('add-field-button'));
38
37
 
39
38
  expect(screen.getByTestId('field-field1')).toBeInTheDocument();
40
39
  });
@@ -51,7 +50,6 @@ describe('FieldSelection', () => {
51
50
  target: { value: 'field1' },
52
51
  });
53
52
  fireEvent.click(screen.getByText('Field 1'));
54
- fireEvent.click(screen.getByTestId('add-field-button'));
55
53
 
56
54
  expect(screen.getByTestId('field-field1')).toBeInTheDocument();
57
55
 
@@ -1,4 +1,5 @@
1
- import React, { useCallback, useEffect } from 'react';
1
+ import React, { useEffect } from 'react';
2
+ import { noop } from '../../helpers/utils';
2
3
  import { Accordion, AccordionItem } from '../Accordion';
3
4
  import { Button, ButtonContext } from '../Buttons';
4
5
  import { Select } from '../FormElements';
@@ -8,6 +9,8 @@ import classes from './FieldSelection.scss';
8
9
 
9
10
  interface FieldSelectionProps {
10
11
  className?: string;
12
+ onFieldAdded?: (field: string) => void;
13
+ onFieldRemoved?: (field: string) => void;
11
14
  }
12
15
 
13
16
  interface FieldDefinition {
@@ -19,6 +22,8 @@ interface FieldDefinition {
19
22
 
20
23
  export const FieldSelection: React.FC<FieldSelectionProps> = ({
21
24
  className,
25
+ onFieldAdded = noop,
26
+ onFieldRemoved = noop,
22
27
  children,
23
28
  }) => {
24
29
  useEffect(() => {
@@ -70,14 +75,15 @@ export const FieldSelection: React.FC<FieldSelectionProps> = ({
70
75
  ...currentFields,
71
76
  newField,
72
77
  ]);
78
+ onFieldAdded(newField.value);
73
79
  }
74
80
  }}
75
81
  />
76
82
  }
77
83
  >
78
- {selectedFields.map((field, i) => (
84
+ {selectedFields.map((field) => (
79
85
  <AccordionItem
80
- key={i}
86
+ key={field.value}
81
87
  header={
82
88
  <FieldSelectionItemHeader
83
89
  label={field.label as string}
@@ -88,6 +94,7 @@ export const FieldSelection: React.FC<FieldSelectionProps> = ({
88
94
  setAvailableFields((currentFields) =>
89
95
  [...currentFields, field].sort((a, b) => a.index - b.index),
90
96
  );
97
+ onFieldRemoved(field.value);
91
98
  }}
92
99
  />
93
100
  }
@@ -105,7 +112,7 @@ const FieldSelectionHeader: React.FC<{
105
112
  }> = ({ fields, onAddField }) => {
106
113
  const [value, setValue] = React.useState<string>();
107
114
 
108
- const handleButtonClicked = useCallback(() => {
115
+ useEffect(() => {
109
116
  if (value) {
110
117
  onAddField(value);
111
118
  setValue(undefined);
@@ -113,22 +120,17 @@ const FieldSelectionHeader: React.FC<{
113
120
  }, [onAddField, value]);
114
121
 
115
122
  return (
116
- <div className={classes.header}>
123
+ <div className={classes.selectionHeader}>
117
124
  <Select
118
125
  label="Field"
119
126
  name="field"
120
127
  placeholder="Select Field"
121
128
  options={fields}
129
+ disabled={fields.length === 0}
122
130
  inlineMode={true}
123
131
  onChange={(e) => setValue(e.currentTarget.value)}
124
132
  value={value}
125
133
  />
126
- <Button
127
- icon={IconName.Plus}
128
- buttonContext={value ? ButtonContext.Active : ButtonContext.Icon}
129
- onButtonClicked={handleButtonClicked}
130
- dataTestId="add-field-button"
131
- />
132
134
  </div>
133
135
  );
134
136
  };
@@ -1,6 +1,7 @@
1
1
  import clsx from 'clsx';
2
2
  import { DateTime } from 'luxon';
3
3
  import React, { useCallback, useEffect, useRef, useState } from 'react';
4
+ import { noop } from '../../../helpers/utils';
4
5
  import { Button, ButtonContext } from '../../Buttons';
5
6
  import { DateTimePicker } from '../../DateTime/DateTimePicker';
6
7
  import { IconName } from '../../Icons';
@@ -18,7 +19,7 @@ export interface DateTimeTextProps extends BaseFormControl {
18
19
  autoFocus?: boolean;
19
20
  /** Whether or not the control supports auto complete */
20
21
  autoComplete?: 'on' | 'off';
21
- /** Whether the control modifies the time portion of the value */
22
+ /** Whether the control modifies the time portion of the value (default: true) */
22
23
  modifyTime?: boolean;
23
24
  /** Callback when the datepicker popup is closed or the date field value is changed */
24
25
  onChange?: (value: string | null, isValidDate: boolean) => void;
@@ -39,7 +40,7 @@ export const DateTimeText: React.FC<DateTimeTextProps> = ({
39
40
  error = undefined,
40
41
  autoFocus = false,
41
42
  autoComplete,
42
- onChange,
43
+ onChange = noop,
43
44
  modifyTime = true,
44
45
  className = '',
45
46
  ...rest
@@ -60,34 +61,27 @@ export const DateTimeText: React.FC<DateTimeTextProps> = ({
60
61
 
61
62
  const onBlurHandler = useCallback(
62
63
  (e: React.FocusEvent<HTMLInputElement>) => {
63
- // `display` represents the formatted version of the `value` prop and is updated whenever `value` changes.
64
- // Comparing `e.target.value` with `display` ensures that onChange is only triggered when the input value has changed.
65
- if (e.target.value === display) {
66
- // If the value hasn't changed, do not trigger onChange
67
- return;
68
- }
69
-
70
64
  const textValue = e.target.value;
71
65
  const parsedValue = modifyTime
72
66
  ? DateTime.fromFormat(textValue, 'f')
73
67
  : DateTime.fromFormat(textValue, 'D');
74
68
 
75
69
  if (parsedValue.isValid) {
76
- onChange && onChange(parsedValue.toISO(), parsedValue.isValid);
70
+ onChange(parsedValue.toISO(), parsedValue.isValid);
77
71
  } else if (textValue === '') {
78
- onChange && onChange(null, true);
72
+ onChange(null, true);
79
73
  } else {
80
74
  // Fallback parser if the user types in an ISO date
81
75
  const isoParsedValue = DateTime.fromISO(textValue);
82
76
 
83
77
  if (isoParsedValue.isValid) {
84
- onChange && onChange(isoParsedValue.toISO(), isoParsedValue.isValid);
78
+ onChange(isoParsedValue.toISO(), isoParsedValue.isValid);
85
79
  } else {
86
- onChange && onChange(textValue, parsedValue.isValid);
80
+ onChange(textValue, parsedValue.isValid);
87
81
  }
88
82
  }
89
83
  },
90
- [display, modifyTime, onChange],
84
+ [modifyTime, onChange],
91
85
  );
92
86
 
93
87
  return (
@@ -1,4 +1,5 @@
1
1
  import { useField } from 'formik';
2
+ import { DateTime } from 'luxon';
2
3
  import React from 'react';
3
4
  import { useFormikError } from '../useFormikError';
4
5
  import { DateTimeText, DateTimeTextProps } from './DateTimeText';
@@ -14,16 +15,28 @@ export const DateTimeTextField: React.FC<
14
15
  const error = useFormikError(props.name);
15
16
  const [, meta, helpers] = useField(props.name);
16
17
 
17
- const handleChange = (value: string | null, valid?: boolean): void => {
18
+ const handleChange = (value: string | null, valid: boolean): void => {
18
19
  if (!valid) {
19
20
  helpers.setError('Invalid Date');
20
21
  helpers.setValue(value, false);
21
22
  } else {
22
- helpers.setValue(value);
23
- }
23
+ const initialDateTime = DateTime.fromISO(meta.initialValue);
24
+ const newDateTime = DateTime.fromISO(value ?? '');
24
25
 
25
- if (value === meta.initialValue) {
26
- helpers.setTouched(false);
26
+ if (newDateTime.setZone(initialDateTime.zone).equals(initialDateTime)) {
27
+ // If the new date is the same as the initial value and the timezone is the only change,
28
+ // set it to the initial value
29
+ helpers.setValue(meta.initialValue);
30
+ } else if (
31
+ (value === null || value === '') &&
32
+ (!meta.initialValue || meta.initialValue === '')
33
+ ) {
34
+ // If the value is null or empty and the initial value is also null or empty,
35
+ // set it to the initial value
36
+ helpers.setValue(meta.initialValue);
37
+ } else {
38
+ helpers.setValue(value);
39
+ }
27
40
  }
28
41
  };
29
42
 
@@ -7,6 +7,7 @@ import * as Yup from 'yup';
7
7
  import { createGroups } from '../../../helpers/storybook';
8
8
  import {
9
9
  CustomTagsField,
10
+ DateTimeTextField,
10
11
  FileUpload,
11
12
  SelectField,
12
13
  SingleLineTextField,
@@ -23,6 +24,7 @@ interface CreateValues {
23
24
  shortDescription?: string;
24
25
  longDescription?: string;
25
26
  cast?: string[];
27
+ released?: string;
26
28
  }
27
29
 
28
30
  interface APIResponse {
@@ -108,6 +110,12 @@ export const Default: StoryObj<CreateStoryType> = {
108
110
  as={SingleLineTextField}
109
111
  type="password"
110
112
  />
113
+ <Field
114
+ name="released"
115
+ label="Released"
116
+ as={DateTimeTextField}
117
+ modifyTime={false}
118
+ />
111
119
  </>
112
120
  ),
113
121
  title: 'Create a Movie',
@@ -127,6 +135,7 @@ export const Default: StoryObj<CreateStoryType> = {
127
135
  shortDescription: '',
128
136
  longDescription: '',
129
137
  cast: [],
138
+ released: '',
130
139
  },
131
140
  },
132
141
  cancelNavigationUrl: '/',
@@ -169,7 +169,7 @@ export const Extended: StoryObj<typeof Details> = (() => {
169
169
  shortDescription: 'Some short abstract...',
170
170
  longDescription: '',
171
171
  cast: ['Jane Doe', 'John Doe'],
172
- released: '2020-04-03T00:00:00.000+00:00',
172
+ released: '2020-04-03',
173
173
  list: listData,
174
174
  archived: false,
175
175
  timestamp: '00:00:00.001',
@@ -227,7 +227,7 @@ export const Extended: StoryObj<typeof Details> = (() => {
227
227
  name="released"
228
228
  label="Released"
229
229
  as={DateTimeTextField}
230
- // modifyTime={false}
230
+ modifyTime={false}
231
231
  />
232
232
  <Field
233
233
  name="password"
@@ -5,6 +5,7 @@ import { OptionalObjectSchema } from 'yup/lib/object';
5
5
  import { Data } from '../../types/data';
6
6
  import { BulkEditContext } from '../Explorer/BulkEdit/BulkEditContext';
7
7
  import { QuickEditContext } from '../Explorer/QuickEdit/QuickEditContext';
8
+ import { PageHeaderJsActionProps } from '../PageHeader/PageHeaderAction';
8
9
  import { StationMessage } from '../models';
9
10
  import {
10
11
  FormActionData,
@@ -39,6 +40,16 @@ export interface FormStationProps<
39
40
  actionsWidth?: string;
40
41
  /** If set to true, the save header action is shown. (default: true) */
41
42
  showSaveHeaderAction?: boolean;
43
+ /**
44
+ * Optional configuration for the save header action button.
45
+ * Allows customizing the label and icon.
46
+ */
47
+ saveHeaderActionConfig?: Pick<PageHeaderJsActionProps, 'label' | 'icon'>;
48
+ /**
49
+ * If set, this will override the default notification message shown
50
+ * after a successful save.
51
+ */
52
+ saveNotificationMessage?: string;
42
53
  /**
43
54
  * An object containing the initial data of the form.
44
55
  */
@@ -91,6 +102,8 @@ export const FormStation = <TValues extends Data, TSubmitResponse = unknown>({
91
102
  className = '',
92
103
  setTabTitle = true,
93
104
  showSaveHeaderAction = true,
105
+ saveHeaderActionConfig,
106
+ saveNotificationMessage,
94
107
  }: PropsWithChildren<
95
108
  FormStationProps<TValues, TSubmitResponse>
96
109
  >): JSX.Element => {
@@ -103,7 +116,7 @@ export const FormStation = <TValues extends Data, TSubmitResponse = unknown>({
103
116
  setStationError,
104
117
  isFormSubmitting,
105
118
  lastSubmittedResponse,
106
- } = useDataProvider(initialData, saveData);
119
+ } = useDataProvider(initialData, saveData, saveNotificationMessage);
107
120
 
108
121
  const { setValidationError, validationWatcher } = useValidationError(
109
122
  stationError,
@@ -137,6 +150,8 @@ export const FormStation = <TValues extends Data, TSubmitResponse = unknown>({
137
150
  className={classes.header}
138
151
  setTabTitle={setTabTitle}
139
152
  showSaveHeaderAction={showSaveHeaderAction}
153
+ saveHeaderActionConfig={saveHeaderActionConfig}
154
+ setValidationError={setValidationError}
140
155
  />
141
156
  {!bulkEditContext && (
142
157
  <SaveOnNavigate
@@ -10,6 +10,7 @@ import {
10
10
  PageHeaderActionType,
11
11
  PageHeaderProps,
12
12
  } from '../../PageHeader';
13
+ import { PageHeaderJsActionProps } from '../../PageHeader/PageHeaderAction';
13
14
  import { useTitle } from '../helpers/useTitle';
14
15
 
15
16
  /**
@@ -22,6 +23,8 @@ export const FormStationHeader: React.FC<
22
23
  cancelNavigationUrl?: string;
23
24
  setTabTitle?: boolean;
24
25
  showSaveHeaderAction?: boolean;
26
+ saveHeaderActionConfig?: Pick<PageHeaderJsActionProps, 'label' | 'icon'>;
27
+ setValidationError: () => void;
25
28
  }
26
29
  > = ({
27
30
  titleProperty,
@@ -31,8 +34,14 @@ export const FormStationHeader: React.FC<
31
34
  className,
32
35
  setTabTitle,
33
36
  showSaveHeaderAction,
37
+ saveHeaderActionConfig = {
38
+ label: 'Save',
39
+ icon: IconName.Save,
40
+ },
41
+ setValidationError,
34
42
  }) => {
35
- const { dirty, submitForm, resetForm } = useFormikContext<FormikValues>();
43
+ const { dirty, submitForm, resetForm, isValid } =
44
+ useFormikContext<FormikValues>();
36
45
  const quickEditContext = useContext(QuickEditContext);
37
46
  const bulkEditContext = useContext(BulkEditContext);
38
47
 
@@ -56,14 +65,18 @@ export const FormStationHeader: React.FC<
56
65
 
57
66
  if (showSaveHeaderAction) {
58
67
  actionItems.push({
59
- label: 'Save',
60
- icon: IconName.Save,
68
+ label: saveHeaderActionConfig.label,
69
+ icon: saveHeaderActionConfig.icon,
61
70
  kind: 'action',
62
71
  actionType: PageHeaderActionType.Context,
63
72
  onClick: async () => {
64
73
  if (quickEditContext?.isQuickEditMode) {
65
74
  quickEditContext.refresh();
66
75
  } else if (bulkEditContext?.isBulkEditMode) {
76
+ if (!isValid) {
77
+ setValidationError();
78
+ return;
79
+ }
67
80
  await submitForm();
68
81
  history.replace(history.location.pathname);
69
82
  } else {
@@ -114,8 +127,12 @@ export const FormStationHeader: React.FC<
114
127
  cancelNavigationUrl,
115
128
  dirty,
116
129
  history,
130
+ isValid,
117
131
  quickEditContext,
118
132
  resetForm,
133
+ saveHeaderActionConfig.icon,
134
+ saveHeaderActionConfig.label,
135
+ setValidationError,
119
136
  showSaveHeaderAction,
120
137
  submitForm,
121
138
  ]);
@@ -20,6 +20,7 @@ import {
20
20
  export type FormStationDataProvider = <TValues extends Data, TSubmitResponse>(
21
21
  initialData: InitialFormData<TValues>,
22
22
  saveData: SaveDataFunction<TValues, TSubmitResponse>,
23
+ saveNotificationMessage?: string,
23
24
  ) => {
24
25
  onSubmit: (
25
26
  values: TValues,
@@ -38,6 +39,7 @@ export const useDataProvider: FormStationDataProvider = <
38
39
  >(
39
40
  initialData: InitialFormData<TValues>,
40
41
  saveData: SaveDataFunction<TValues, TSubmitResponse>,
42
+ saveNotificationMessage?: string,
41
43
  ) => {
42
44
  const [stationError, setStationError] = useState<StationErrorStateType>();
43
45
  const [isFormSubmitting, setIsFormSubmitting] = useState<boolean>(false);
@@ -92,7 +94,9 @@ export const useDataProvider: FormStationDataProvider = <
92
94
  }
93
95
 
94
96
  showNotification({
95
- title: 'Your changes were saved successfully.',
97
+ title:
98
+ saveNotificationMessage ??
99
+ 'Your changes were saved successfully.',
96
100
  options: {
97
101
  type: 'success',
98
102
  autoClose: 1500,
@@ -115,7 +119,7 @@ export const useDataProvider: FormStationDataProvider = <
115
119
  setIsFormSubmitting(false);
116
120
  }
117
121
  },
118
- [isFormSubmitting, initialData, saveData, setStationError],
122
+ [isFormSubmitting, initialData, saveData, saveNotificationMessage],
119
123
  );
120
124
 
121
125
  return {
@@ -56,4 +56,5 @@ export enum IconName {
56
56
  Video,
57
57
  Warning,
58
58
  X,
59
+ Axi,
59
60
  }
@@ -10,6 +10,22 @@ export interface IconsProps {
10
10
  className?: string;
11
11
  }
12
12
 
13
+ const AxiIcon: React.FC<{ className?: string }> = ({ className }) => (
14
+ <svg
15
+ className={clsx(classes.icons, className)}
16
+ version="1.1"
17
+ xmlns="http://www.w3.org/2000/svg"
18
+ viewBox="0 0 40 40"
19
+ >
20
+ <path
21
+ vectorEffect="non-scaling-stroke"
22
+ fill="none"
23
+ strokeWidth="2"
24
+ d="M24.3,36h-8.5c-5,0-9.1-4.1-9.1-9.1v-5.3c0-5,4.1-9.1,9.1-9.1h8.5c5,0,9.1,4.1,9.1,9.1v5.3c0,5-4.1,9.1-9.1,9.1ZM6.8,20.4h-.9c-1.3,0-2.3,1.1-2.3,2.3v3c0,1.3,1.1,2.3,2.3,2.3h.9M33.3,28.2h.9c1.3,0,2.3-1.1,2.3-2.3v-3c0-1.3-1.1-2.3-2.3-2.3h-.9M14.1,20.4c-1.4,0-2.5,1.1-2.5,2.5s1.1,2.5,2.5,2.5,2.5-1.1,2.5-2.5-1.1-2.5-2.5-2.5ZM19.7,4c-1.4,0-2.5,1.1-2.5,2.5s1.1,2.5,2.5,2.5,2.5-1.1,2.5-2.5-1.1-2.5-2.5-2.5ZM25.9,20.4c-1.4,0-2.5,1.1-2.5,2.5s1.1,2.5,2.5,2.5,2.5-1.1,2.5-2.5-1.1-2.5-2.5-2.5ZM19.7,9v3.5M16.1,30.2h7.8"
25
+ />
26
+ </svg>
27
+ );
28
+
13
29
  const ArchiveIcon: React.FC<{ className?: string }> = ({ className }) => (
14
30
  <svg
15
31
  className={clsx(classes.icons, className)}
@@ -924,6 +940,7 @@ const XIcon: React.FC<{ className?: string }> = ({ className }) => (
924
940
  */
925
941
  export const Icons: React.FC<IconsProps> = ({ icon, className }) => {
926
942
  const icons: { [key in IconName]: JSX.Element } = {
943
+ [IconName.Axi]: <AxiIcon className={className} />,
927
944
  [IconName.Archive]: <ArchiveIcon className={className} />,
928
945
  [IconName.Audio]: <AudioIcon className={className} />,
929
946
  [IconName.DescriptiveAudio]: <DescriptiveAudioIcon className={className} />,
@@ -25,6 +25,13 @@ const headerActions: PageHeaderActionItemProps[] = [
25
25
  ];
26
26
 
27
27
  const bulkHeaderActions: PageHeaderActionProps[] = [
28
+ {
29
+ label: 'Bulk Edit',
30
+ actionType: PageHeaderActionType.Context,
31
+ icon: IconName.BulkEdit,
32
+ tag: 'BETA',
33
+ onClick: action('onActionSelected'),
34
+ },
28
35
  {
29
36
  label: 'Bulk Delete',
30
37
  actionType: PageHeaderActionType.Context,
@@ -67,6 +74,7 @@ const bulkHeaderActions: PageHeaderActionProps[] = [
67
74
  label: 'Bulk Action 4',
68
75
  confirmationMode: 'Simple',
69
76
  onClick: action('onActionSelected'),
77
+ tag: 'BETA',
70
78
  },
71
79
  ];
72
80
 
@@ -48,4 +48,6 @@ interface BaseActionOptions {
48
48
  disabled?: boolean;
49
49
  /** Optional class */
50
50
  className?: string;
51
+ /** Optional tag to display in the action */
52
+ tag?: string;
51
53
  }
@@ -163,4 +163,32 @@
163
163
 
164
164
  color: var(--actions-disabled-fg-color, $actions-disabled-fg-color);
165
165
  }
166
+
167
+ .tag {
168
+ position: absolute;
169
+ transform: rotate(45deg) translateX(53px) translateY(-40px);
170
+ transform-origin: center;
171
+ top: 0;
172
+ left: 0;
173
+ display: grid;
174
+ background-color: var(
175
+ --page-header-action-tag-background-color,
176
+ $page-header-action-tag-background-color
177
+ );
178
+ font-size: var(
179
+ --page-header-action-tag-font-size,
180
+ $page-header-action-tag-font-size
181
+ );
182
+ font-weight: bold;
183
+ width: 75px;
184
+ height: 14px;
185
+ align-items: center;
186
+ text-align: center;
187
+ z-index: 0;
188
+ }
189
+
190
+ &:has(.tag) {
191
+ position: relative;
192
+ overflow: hidden;
193
+ }
166
194
  }
@@ -645,16 +645,6 @@ describe('PageHeaderAction', () => {
645
645
  const link = wrapper.find('Link');
646
646
  expect(link.prop('target')).toBeUndefined();
647
647
  });
648
-
649
- it('renders with disabled class if disabled prop is true', () => {
650
- const wrapper = mount(
651
- <Router>
652
- <PageHeaderAction {...defaultProps} path="/test" disabled />
653
- </Router>,
654
- );
655
- const container = wrapper.find('.container').hostNodes();
656
- expect(container.hasClass('disabled')).toBe(true);
657
- });
658
648
  });
659
649
  });
660
650
 
@@ -69,6 +69,7 @@ const PageHeaderJSAction: React.FC<PageHeaderJsActionProps> = ({
69
69
  disabled,
70
70
  className,
71
71
  onClick = noop,
72
+ tag,
72
73
  }) => {
73
74
  const [confirmation, setConfirmation] = useState<boolean>(false);
74
75
  const [referenceElement, setReferenceElement] = useState<HTMLElement>();
@@ -156,10 +157,10 @@ const PageHeaderJSAction: React.FC<PageHeaderJsActionProps> = ({
156
157
  <span data-test-id="label">{label}</span>
157
158
  )}
158
159
  </div>
160
+ {tag && <div className={classes.tag}>{tag}</div>}
159
161
  </button>
160
162
  {confirmation && confirmationMode === 'Advanced' && (
161
163
  <ConfirmDialog
162
- className={classes.confirmDialog}
163
164
  referenceElement={referenceElement}
164
165
  title={title}
165
166
  cancelButtonText={cancelButtonText}
@@ -185,12 +186,12 @@ const PageHeaderJSAction: React.FC<PageHeaderJsActionProps> = ({
185
186
  */
186
187
  const PageHeaderNavigationAction: React.FC<PageHeaderNavigationActionProps> = ({
187
188
  className,
188
- disabled,
189
189
  icon,
190
190
  imgAlt,
191
191
  label,
192
192
  openInNewTab = false,
193
193
  path,
194
+ tag,
194
195
  }) => {
195
196
  const headerIcon = icon ? icon : openInNewTab ? IconName.External : undefined;
196
197
 
@@ -199,7 +200,6 @@ const PageHeaderNavigationAction: React.FC<PageHeaderNavigationActionProps> = ({
199
200
  className={clsx(
200
201
  classes.container,
201
202
  'page-header-action-container',
202
- { [classes.disabled]: disabled },
203
203
  className,
204
204
  )}
205
205
  data-test-id="action"
@@ -216,6 +216,7 @@ const PageHeaderNavigationAction: React.FC<PageHeaderNavigationActionProps> = ({
216
216
  <div className={classes.label}>
217
217
  <span data-test-id="label">{label}</span>
218
218
  </div>
219
+ {tag && <div className={classes.tag}>{tag}</div>}
219
220
  </Link>
220
221
  );
221
222
  };