@stack-spot/portal-components 2.21.2 → 2.22.0

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.22.0](https://github.com/stack-spot/portal-commons/compare/portal-components@v2.21.2...portal-components@v2.22.0) (2025-04-25)
4
+
5
+
6
+ ### Features
7
+
8
+ * add component form ([fbc608d](https://github.com/stack-spot/portal-commons/commit/fbc608d0b2aaa8f99fc154a6a79141984c93daf3))
9
+
3
10
  ## [2.21.2](https://github.com/stack-spot/portal-commons/compare/portal-components@v2.21.1...portal-components@v2.21.2) (2025-04-24)
4
11
 
5
12
 
@@ -26,6 +26,10 @@ export interface StepperContextProps {
26
26
  * Navigate to the previous step.
27
27
  */
28
28
  goToPreviousStep: () => void;
29
+ /**
30
+ * Navigate to the provided step.
31
+ */
32
+ goToStep: (step: number) => void;
29
33
  }
30
34
  declare const StepperContext: import("react").Context<StepperContextProps>;
31
35
  export interface StepperProps {
@@ -1 +1 @@
1
- {"version":3,"file":"Stepper.d.ts","sourceRoot":"","sources":["../../../src/components/Stepper/Stepper.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,EAAE,EAAE,YAAY,EAA2B,MAAM,OAAO,CAAA;AAGjE,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AAElC,MAAM,WAAW,mBAAmB;IAClC;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC/B;;OAEG;IACH,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IAC/C;;OAEG;IACH,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,GAAG,GAAG,IAAI,GAAG,SAAS,CAAC;IACtD;;OAEG;IACH,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB;;OAEG;IACH,gBAAgB,EAAE,MAAM,IAAI,CAAC;CAC9B;AAED,QAAA,MAAM,cAAc,8CAOlB,CAAA;AAEF,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,YAAY,CAAC,SAAS,CAAC,EAAE,CAAC;CACrC;AAWD;;;;GAIG;AAEH,QAAA,MAAM,OAAO,EAAE,EAAE,CAAC,YAAY,CA0C7B,CAAA;AACD,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,CAAA"}
1
+ {"version":3,"file":"Stepper.d.ts","sourceRoot":"","sources":["../../../src/components/Stepper/Stepper.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,EAAE,EAAE,YAAY,EAA2B,MAAM,OAAO,CAAA;AAGjE,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AAElC,MAAM,WAAW,mBAAmB;IAClC;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC/B;;OAEG;IACH,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IAC/C;;OAEG;IACH,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,GAAG,GAAG,IAAI,GAAG,SAAS,CAAC;IACtD;;OAEG;IACH,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB;;OAEG;IACH,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B;;OAEG;IACH,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC;AAED,QAAA,MAAM,cAAc,8CAQlB,CAAA;AAEF,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,YAAY,CAAC,SAAS,CAAC,EAAE,CAAC;CACrC;AAWD;;;;GAIG;AAEH,QAAA,MAAM,OAAO,EAAE,EAAE,CAAC,YAAY,CA2C7B,CAAA;AACD,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,CAAA"}
@@ -10,6 +10,7 @@ const StepperContext = createContext({
10
10
  getStepData: () => { },
11
11
  goToNextStep: () => { },
12
12
  goToPreviousStep: () => { },
13
+ goToStep: () => { },
13
14
  });
14
15
  const Wrapper = styled(Box) `
15
16
  width: 100%;
@@ -48,6 +49,7 @@ const Stepper = ({ children }) => {
48
49
  getStepData,
49
50
  goToNextStep: setNextStep,
50
51
  goToPreviousStep: setPreviousStep,
52
+ goToStep: setActiveIndex,
51
53
  };
52
54
  return (_jsx(StepperContext.Provider, { value: contextValue, children: _jsxs(Wrapper, { children: [_jsx(StepperHeaders, { headers }), _jsx(ContentWrapper, { children: children[activeIndex] })] }) }));
53
55
  };
@@ -1 +1 @@
1
- {"version":3,"file":"Stepper.js","sourceRoot":"","sources":["../../../src/components/Stepper/Stepper.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,cAAc,CAAA;AACxC,OAAO,EAAoB,aAAa,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AACjE,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AA+B1C,MAAM,cAAc,GAAG,aAAa,CAAsB;IACxD,WAAW,EAAE,CAAC;IACd,SAAS,EAAE,EAAE;IACb,WAAW,EAAE,GAAG,EAAE,GAAE,CAAC;IACrB,WAAW,EAAE,GAAG,EAAE,GAAE,CAAC;IACrB,YAAY,EAAE,GAAG,EAAE,GAAE,CAAC;IACtB,gBAAgB,EAAE,GAAG,EAAE,GAAE,CAAC;CAC3B,CAAC,CAAA;AAMF,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,CAAA;;;CAG1B,CAAA;AAED,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAC,CAAA;;CAElC,CAAA;AAED;;;;GAIG;AAEH,MAAM,OAAO,GAAqB,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE;IACjD,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAS,CAAC,CAAC,CAAA;IACzD,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAsB,EAAE,CAAC,CAAA;IACnE,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAA;IAEvE,MAAM,WAAW,GAAG,CAAC,IAAY,EAAE,IAAS,EAAE,EAAE;QAC9C,YAAY,CAAC,EAAE,GAAG,SAAS,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,CAAA;IACrD,CAAC,CAAA;IAED,MAAM,WAAW,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;IAErD,MAAM,WAAW,GAAG,GAAG,EAAE;QACvB,IAAI,WAAW,GAAG,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC;YACxC,cAAc,CAAC,WAAW,GAAG,CAAC,CAAC,CAAA;QACjC,CAAC;IACH,CAAC,CAAA;IAED,MAAM,eAAe,GAAG,GAAG,EAAE;QAC3B,IAAI,WAAW,GAAG,CAAC,EAAE,CAAC;YACpB,cAAc,CAAC,WAAW,GAAG,CAAC,CAAC,CAAA;QACjC,CAAC;IACH,CAAC,CAAA;IAED,MAAM,YAAY,GAAwB;QACxC,WAAW;QACX,SAAS;QACT,WAAW;QACX,WAAW;QACX,YAAY,EAAE,WAAW;QACzB,gBAAgB,EAAE,eAAe;KAClC,CAAA;IAED,OAAO,CACL,KAAC,cAAc,CAAC,QAAQ,IAAC,KAAK,EAAE,YAAY,YAC1C,MAAC,OAAO,eACN,KAAC,cAAc,IAAO,OAAO,GAAM,EACnC,KAAC,cAAc,cACZ,QAAQ,CAAC,WAAW,CAAC,GACP,IACT,GACc,CAC3B,CAAA;AACH,CAAC,CAAA;AACD,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,CAAA"}
1
+ {"version":3,"file":"Stepper.js","sourceRoot":"","sources":["../../../src/components/Stepper/Stepper.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,cAAc,CAAA;AACxC,OAAO,EAAoB,aAAa,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AACjE,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAmC1C,MAAM,cAAc,GAAG,aAAa,CAAsB;IACxD,WAAW,EAAE,CAAC;IACd,SAAS,EAAE,EAAE;IACb,WAAW,EAAE,GAAG,EAAE,GAAE,CAAC;IACrB,WAAW,EAAE,GAAG,EAAE,GAAE,CAAC;IACrB,YAAY,EAAE,GAAG,EAAE,GAAE,CAAC;IACtB,gBAAgB,EAAE,GAAG,EAAE,GAAE,CAAC;IAC1B,QAAQ,EAAE,GAAG,EAAE,GAAE,CAAC;CACnB,CAAC,CAAA;AAMF,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,CAAA;;;CAG1B,CAAA;AAED,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAC,CAAA;;CAElC,CAAA;AAED;;;;GAIG;AAEH,MAAM,OAAO,GAAqB,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE;IACjD,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAS,CAAC,CAAC,CAAA;IACzD,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAsB,EAAE,CAAC,CAAA;IACnE,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAA;IAEvE,MAAM,WAAW,GAAG,CAAC,IAAY,EAAE,IAAS,EAAE,EAAE;QAC9C,YAAY,CAAC,EAAE,GAAG,SAAS,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,CAAA;IACrD,CAAC,CAAA;IAED,MAAM,WAAW,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;IAErD,MAAM,WAAW,GAAG,GAAG,EAAE;QACvB,IAAI,WAAW,GAAG,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC;YACxC,cAAc,CAAC,WAAW,GAAG,CAAC,CAAC,CAAA;QACjC,CAAC;IACH,CAAC,CAAA;IAED,MAAM,eAAe,GAAG,GAAG,EAAE;QAC3B,IAAI,WAAW,GAAG,CAAC,EAAE,CAAC;YACpB,cAAc,CAAC,WAAW,GAAG,CAAC,CAAC,CAAA;QACjC,CAAC;IACH,CAAC,CAAA;IAED,MAAM,YAAY,GAAwB;QACxC,WAAW;QACX,SAAS;QACT,WAAW;QACX,WAAW;QACX,YAAY,EAAE,WAAW;QACzB,gBAAgB,EAAE,eAAe;QACjC,QAAQ,EAAE,cAAc;KACzB,CAAA;IAED,OAAO,CACL,KAAC,cAAc,CAAC,QAAQ,IAAC,KAAK,EAAE,YAAY,YAC1C,MAAC,OAAO,eACN,KAAC,cAAc,IAAO,OAAO,GAAM,EACnC,KAAC,cAAc,cACZ,QAAQ,CAAC,WAAW,CAAC,GACP,IACT,GACc,CAC3B,CAAA;AACH,CAAC,CAAA;AACD,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,CAAA"}
@@ -0,0 +1,15 @@
1
+ import { ReactNode } from 'react';
2
+ import { FieldValues, UseFormReturn } from 'react-hook-form';
3
+ export interface FormProps {
4
+ id?: string;
5
+ name: string;
6
+ defaultValues?: FieldValues;
7
+ autoComplete?: HTMLInputElement['autocomplete'];
8
+ children?: ReactNode;
9
+ onSubmit?: (params: FieldValues) => unknown;
10
+ form?: UseFormReturn<any, any>;
11
+ style?: React.CSSProperties;
12
+ className?: string;
13
+ }
14
+ export declare const Form: (props: FormProps) => import("react/jsx-runtime").JSX.Element;
15
+ //# sourceMappingURL=Form.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Form.d.ts","sourceRoot":"","sources":["../../../../src/components/form/Form/Form.tsx"],"names":[],"mappings":"AACA,OAAO,EAAa,SAAS,EAAE,MAAM,OAAO,CAAA;AAC5C,OAAO,EAAE,WAAW,EAAgB,aAAa,EAA2B,MAAM,iBAAiB,CAAA;AAEnG,MAAM,WAAW,SAAS;IACxB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,WAAW,CAAC;IAC5B,YAAY,CAAC,EAAE,gBAAgB,CAAC,cAAc,CAAC,CAAC;IAChD,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC;IAC5C,IAAI,CAAC,EAAE,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAC/B,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA4ED,eAAO,MAAM,IAAI,GAAI,OAAO,SAAS,4CASpC,CAAA"}
@@ -0,0 +1,48 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { get } from 'lodash';
3
+ import { FormProvider, useForm, useFormContext } from 'react-hook-form';
4
+ function handleGraphqlErrors(error, methods) {
5
+ const errors = get(error, 'cause.extensions.fields');
6
+ if (errors) {
7
+ Object.keys(errors).forEach((key) => {
8
+ if (Object.keys(methods.control._fields).includes(key)) {
9
+ methods.setError(key, { type: 'server', message: errors[`${key}`] });
10
+ }
11
+ });
12
+ }
13
+ }
14
+ const handleFormSubmit = async (data, methods, onSubmit) => {
15
+ if (!onSubmit) {
16
+ return;
17
+ }
18
+ const result = methods.handleSubmit(onSubmit)(data);
19
+ const isPromise = result instanceof Promise;
20
+ if (!isPromise) {
21
+ return result;
22
+ }
23
+ try {
24
+ return await result;
25
+ }
26
+ catch (error) {
27
+ const isGraphqlError = error.message === 'GraphqlError';
28
+ if (!isGraphqlError) {
29
+ throw error;
30
+ }
31
+ handleGraphqlErrors(error, methods);
32
+ }
33
+ };
34
+ const BasicForm = ({ onSubmit, id, name, children, methods, autoComplete, ...props }) => (_jsx("form", { id: id, autoComplete: autoComplete, name: name, onSubmit: (data) => handleFormSubmit(data, methods, onSubmit), noValidate: true, style: props.style, className: props.className, children: children }));
35
+ const FormWithProvider = (props) => {
36
+ const methods = useForm({ defaultValues: props.defaultValues });
37
+ const form = props.form || methods;
38
+ return (_jsx(FormProvider, { ...form, children: _jsx(BasicForm, { ...props, methods: form }) }));
39
+ };
40
+ export const Form = (props) => {
41
+ const useFormMethods = useFormContext();
42
+ const hasFormProvider = !!useFormMethods;
43
+ if (hasFormProvider) {
44
+ return _jsx(BasicForm, { ...props, methods: useFormMethods });
45
+ }
46
+ return _jsx(FormWithProvider, { ...props });
47
+ };
48
+ //# sourceMappingURL=Form.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Form.js","sourceRoot":"","sources":["../../../../src/components/form/Form/Form.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import { SxProperties } from '@citric/core/dist/sx.js';
2
+ import { Tooltip } from '@citric/ui';
3
+ import { ChangeEvent, ComponentProps, ComponentPropsWithoutRef, ElementType, ReactElement, ReactNode } from 'react';
4
+ import { RegisterOptions } from 'react-hook-form';
5
+ interface FormGroupProps<T extends ElementType> {
6
+ fieldAddonAfter?: ReactNode;
7
+ fieldAddonBefore?: ReactNode;
8
+ formHelper?: ReactNode;
9
+ name: string;
10
+ id?: string;
11
+ component?: T;
12
+ label?: string | ReactElement;
13
+ registerOptions?: RegisterOptions;
14
+ formItemSx?: SxProperties;
15
+ formGroupSx?: SxProperties;
16
+ componentSx?: SxProperties;
17
+ fieldAddonBeforeSx?: SxProperties;
18
+ fieldAddonAfterSx?: SxProperties;
19
+ $formName?: string;
20
+ required?: boolean;
21
+ onChange?: (event: ChangeEvent<HTMLInputElement> | ChangeEvent<HTMLSelectElement>) => void;
22
+ tooltipProps?: Omit<ComponentProps<typeof Tooltip>, 'children'>;
23
+ readOnly?: boolean;
24
+ disabled?: boolean;
25
+ standalone?: boolean;
26
+ delay?: number;
27
+ hideRequiredSymbol?: boolean;
28
+ shouldUnregister?: boolean;
29
+ }
30
+ type Props<T extends ElementType> = FormGroupProps<T> & Omit<ComponentPropsWithoutRef<T>, keyof FormGroupProps<T>>;
31
+ export declare const FormGroup: <T extends ElementType>({ fieldAddonAfter, fieldAddonBefore, formHelper, id, name, component, label, registerOptions, formGroupSx, formItemSx, componentSx, disabled, readOnly, onChange, tooltipProps, required, colorScheme: propColorScheme, fieldAddonBeforeSx, fieldAddonAfterSx, standalone, delay, hideRequiredSymbol, shouldUnregister, ...props }: Props<T>) => import("react/jsx-runtime").JSX.Element;
32
+ export declare const FormItemError: ({ name, id }: {
33
+ name: string;
34
+ id: string;
35
+ }) => import("react/jsx-runtime").JSX.Element | null;
36
+ export {};
37
+ //# sourceMappingURL=FormGroup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FormGroup.d.ts","sourceRoot":"","sources":["../../../../src/components/form/Form/FormGroup.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAEnD,OAAO,EAAgD,OAAO,EAAE,MAAM,YAAY,CAAA;AAGlF,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,wBAAwB,EAAE,WAAW,EAAE,YAAY,EAAE,SAAS,EAAa,MAAM,OAAO,CAAA;AAC9H,OAAO,EAA0B,eAAe,EAA0E,MAAM,iBAAiB,CAAA;AA2BjJ,UAAU,cAAc,CAAC,CAAC,SAAS,WAAW;IAC5C,eAAe,CAAC,EAAE,SAAS,CAAC;IAC5B,gBAAgB,CAAC,EAAE,SAAS,CAAC;IAC7B,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,CAAC,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,GAAG,YAAY,CAAC;IAC9B,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,UAAU,CAAC,EAAE,YAAY,CAAC;IAC1B,WAAW,CAAC,EAAE,YAAY,CAAC;IAC3B,WAAW,CAAC,EAAE,YAAY,CAAC;IAC3B,kBAAkB,CAAC,EAAE,YAAY,CAAC;IAClC,iBAAiB,CAAC,EAAE,YAAY,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,CAAC,gBAAgB,CAAC,GAAG,WAAW,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAC;IAC3F,YAAY,CAAC,EAAE,IAAI,CAAC,cAAc,CAAC,OAAO,OAAO,CAAC,EAAE,UAAU,CAAC,CAAC;IAChE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,KAAK,KAAK,CAAC,CAAC,SAAS,WAAW,IAAI,cAAc,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,wBAAwB,CAAC,CAAC,CAAC,EAAE,MAAM,cAAc,CAAC,CAAC,CAAC,CAAC,CAAA;AAElH,eAAO,MAAM,SAAS,GAAI,CAAC,SAAS,WAAW,EAAE,oUAyB9C,KAAK,CAAC,CAAC,CAAC,4CA8FV,CAAA;AAED,eAAO,MAAM,aAAa,GAAI,cAAc;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,mDAoBvE,CAAA"}
@@ -0,0 +1,72 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box, Flex, IconBox, Input, Label } from '@citric/core';
3
+ import { InfoCircle, TimesCircle } from '@citric/icons';
4
+ import { FieldAddon, FieldGroup, FormHelper, FormItem, Tooltip } from '@citric/ui';
5
+ import { useTranslate } from '@stack-spot/portal-translate';
6
+ import { debounce, get } from 'lodash';
7
+ import { useEffect } from 'react';
8
+ import { useFormContext } from 'react-hook-form';
9
+ const registerFieldWithDebounceChangeValidation = (name, delay, trigger, register, options) => {
10
+ const useFormRegisterReturn = register(name, options);
11
+ const { onChange } = useFormRegisterReturn;
12
+ const debouncedValidate = debounce(() => {
13
+ trigger(name);
14
+ }, delay);
15
+ const debouncedChange = debounce((event) => {
16
+ onChange?.(event);
17
+ }, delay);
18
+ return {
19
+ ...useFormRegisterReturn,
20
+ onChange: (e) => {
21
+ debouncedChange(e);
22
+ debouncedValidate();
23
+ },
24
+ };
25
+ };
26
+ export const FormGroup = ({ fieldAddonAfter, fieldAddonBefore, formHelper, id, name, component, label, registerOptions = {}, formGroupSx = {}, formItemSx = {}, componentSx = {}, disabled, readOnly, onChange, tooltipProps, required, colorScheme: propColorScheme, fieldAddonBeforeSx, fieldAddonAfterSx, standalone, delay, hideRequiredSymbol, shouldUnregister = false, ...props }) => {
27
+ const t = useTranslate(dictionary);
28
+ const { register, getFieldState, trigger, unregister } = useFormContext();
29
+ const Component = component || Input;
30
+ const colorScheme = propColorScheme;
31
+ let formRegistry = {};
32
+ if (!standalone) {
33
+ const options = {
34
+ onChange,
35
+ required: { message: hideRequiredSymbol ? t.requiredTest : t.required, value: !!required }, validate: (value) => {
36
+ if (required && typeof value === 'string') {
37
+ return !!value?.trim();
38
+ }
39
+ },
40
+ ...registerOptions,
41
+ };
42
+ formRegistry = delay ? registerFieldWithDebounceChangeValidation(name, delay, trigger, register, options) : register(name, options);
43
+ }
44
+ useEffect(() => () => {
45
+ shouldUnregister && unregister(name);
46
+ }, [name, unregister]);
47
+ return (_jsxs(FormItem, { sx: formItemSx, children: [label && (_jsxs(Flex, { children: [_jsxs(Label, { appearance: "body2", htmlFor: id || name, sx: { pointerEvents: disabled || readOnly ? 'none' : undefined }, children: [label, "\u00A0", required && !hideRequiredSymbol && '*'] }), tooltipProps?.text && (_jsx(Tooltip, { ...tooltipProps, children: _jsx(IconBox, { children: _jsx(InfoCircle, {}) }) }))] })), !fieldAddonBefore && !fieldAddonAfter ? (_jsx(Component, { id: id || name, disabled: disabled, readOnly: readOnly, ...props, colorScheme, ...formRegistry, required: required, sx: componentSx, "aria-errormessage": `err${(id || name)}`, "aria-invalid": getFieldState(name).invalid })) : (_jsxs(FieldGroup, { sx: formGroupSx, children: [fieldAddonBefore && (_jsx(FieldAddon, { sx: { borderColor: colorScheme ? colorScheme : 'light.600', ...fieldAddonBeforeSx }, children: fieldAddonBefore })), _jsx(Component, { disabled: disabled, readOnly: readOnly, id: id || name, ...props, colorScheme, ...formRegistry, required: required, sx: componentSx, "aria-errormessage": `err${(id || name)}`, "aria-invalid": getFieldState(name).invalid }), fieldAddonAfter && (_jsx(FieldAddon, { sx: { borderColor: colorScheme ? colorScheme : 'light.600', ...fieldAddonAfterSx }, children: fieldAddonAfter }))] })), _jsx(FormItemError, { name: name, id: id || name }), formHelper && (_jsx(Box, { mt: "2", children: _jsx(FormHelper, { children: formHelper }) }))] }));
48
+ };
49
+ export const FormItemError = ({ name, id }) => {
50
+ const t = useTranslate(dictionary);
51
+ const { formState } = useFormContext();
52
+ const formItemError = get(formState.errors, name);
53
+ if (formItemError) {
54
+ return (_jsxs(Flex, { alignItems: "center", flexWrap: "nowrap", children: [_jsx(IconBox, { size: "xs", appearance: "circle", colorIcon: "danger", colorBg: "danger.500", children: _jsx(TimesCircle, {}) }), _jsx(Box, { ml: "3", children: _jsx(FormHelper, { colorScheme: "danger", "aria-live": "assertive", id: `err${id}`, children: formItemError.message?.toString() || formItemError?.root?.message?.toString() || t.invalidGenericError }) })] }));
55
+ }
56
+ return null;
57
+ };
58
+ const dictionary = {
59
+ en: {
60
+ invalidGenericError: 'Invalid field',
61
+ required: 'Required field',
62
+ // text for tests of usability
63
+ requiredTest: 'Fill in the required field to continue.',
64
+ },
65
+ pt: {
66
+ invalidGenericError: 'Campo inválido',
67
+ required: 'Campo obrigatório',
68
+ // text for tests of usability
69
+ requiredTest: 'Preencha este campo obrigatório para continuar.',
70
+ },
71
+ };
72
+ //# sourceMappingURL=FormGroup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FormGroup.js","sourceRoot":"","sources":["../../../../src/components/form/Form/FormGroup.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export { FormGroup } from './FormGroup.js';
2
+ export { Form } from './Form.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/form/Form/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA"}
@@ -0,0 +1,3 @@
1
+ export { FormGroup } from './FormGroup.js';
2
+ export { Form } from './Form.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/components/form/Form/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stack-spot/portal-components",
3
- "version": "2.21.2",
3
+ "version": "2.22.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -26,7 +26,8 @@
26
26
  "./FileTreeView": "./dist/components/FileTreeView/index.js",
27
27
  "./FadingOverflow": "./dist/components/FadingOverflow.js",
28
28
  "./Table": "./dist/components/Table/index.js",
29
- "./ContentValidateFilter": "./dist/components/ContentValidateFilter.js"
29
+ "./ContentValidateFilter": "./dist/components/ContentValidateFilter.js",
30
+ "./Form": "./dist/components/form/Form/index.js"
30
31
  },
31
32
  "scripts": {
32
33
  "build": "rimraf dist && tsc && tsc-esm-fix --target='dist'",
@@ -68,6 +69,7 @@
68
69
  "dependencies": {
69
70
  "date-fns": "^3.6.0",
70
71
  "lodash": "^4.17.21",
72
+ "react-hook-form": "^7.55.0",
71
73
  "react-infinite-scroll-component": "^6.1.0",
72
74
  "react-markdown": "^9.0.1",
73
75
  "react-select": "^5.10.0",
@@ -30,6 +30,10 @@ export interface StepperContextProps {
30
30
  * Navigate to the previous step.
31
31
  */
32
32
  goToPreviousStep: () => void,
33
+ /**
34
+ * Navigate to the provided step.
35
+ */
36
+ goToStep: (step: number) => void,
33
37
  }
34
38
 
35
39
  const StepperContext = createContext<StepperContextProps>({
@@ -39,6 +43,7 @@ const StepperContext = createContext<StepperContextProps>({
39
43
  getStepData: () => {},
40
44
  goToNextStep: () => {},
41
45
  goToPreviousStep: () => {},
46
+ goToStep: () => {},
42
47
  })
43
48
 
44
49
  export interface StepperProps {
@@ -90,6 +95,7 @@ const Stepper: FC<StepperProps> = ({ children }) => {
90
95
  getStepData,
91
96
  goToNextStep: setNextStep,
92
97
  goToPreviousStep: setPreviousStep,
98
+ goToStep: setActiveIndex,
93
99
  }
94
100
 
95
101
  return (
@@ -0,0 +1,101 @@
1
+ import { get } from 'lodash'
2
+ import { FormEvent, ReactNode } from 'react'
3
+ import { FieldValues, FormProvider, UseFormReturn, useForm, useFormContext } from 'react-hook-form'
4
+
5
+ export interface FormProps {
6
+ id?: string,
7
+ name: string,
8
+ defaultValues?: FieldValues,
9
+ autoComplete?: HTMLInputElement['autocomplete'],
10
+ children?: ReactNode,
11
+ onSubmit?: (params: FieldValues) => unknown,
12
+ form?: UseFormReturn<any, any>,
13
+ style?: React.CSSProperties,
14
+ className?: string,
15
+ }
16
+
17
+
18
+ function handleGraphqlErrors(error: unknown, methods: UseFormReturn<FieldValues>) {
19
+ const errors: any = get(error, 'cause.extensions.fields')
20
+
21
+ if (errors) {
22
+ Object.keys(errors).forEach((key) => {
23
+ if (Object.keys(methods.control._fields).includes(key)) {
24
+ methods.setError(key, { type: 'server', message: errors[`${key}`] })
25
+ }
26
+ })
27
+ }
28
+ }
29
+
30
+ const handleFormSubmit = async (
31
+ data: FormEvent<HTMLFormElement>,
32
+ methods: UseFormReturn,
33
+ onSubmit?: (params: Record<string, any>) => Promise<unknown> | unknown,
34
+ ) => {
35
+
36
+ if (!onSubmit) {
37
+ return
38
+ }
39
+
40
+ const result = methods.handleSubmit(onSubmit)(data)
41
+ const isPromise = result instanceof Promise
42
+
43
+ if (!isPromise) {
44
+ return result
45
+ }
46
+
47
+ try {
48
+ return await result
49
+ } catch (error: any) {
50
+ const isGraphqlError = error.message === 'GraphqlError'
51
+ if (!isGraphqlError) {
52
+ throw error
53
+ }
54
+ handleGraphqlErrors(error, methods)
55
+ }
56
+ }
57
+
58
+ const BasicForm = ({
59
+ onSubmit,
60
+ id,
61
+ name,
62
+ children,
63
+ methods,
64
+ autoComplete,
65
+ ...props
66
+ }: FormProps & { methods: UseFormReturn<FieldValues> }) => (
67
+ <form
68
+ id={id}
69
+ autoComplete={autoComplete}
70
+ name={name}
71
+ onSubmit={(data: FormEvent<HTMLFormElement>) => handleFormSubmit(data, methods, onSubmit)}
72
+ noValidate
73
+ style={props.style}
74
+ className={props.className}
75
+ >
76
+ {children}
77
+ </form>
78
+ )
79
+
80
+ const FormWithProvider = (props: FormProps) => {
81
+ const methods = useForm({ defaultValues: props.defaultValues })
82
+ const form = props.form || methods
83
+
84
+ return (
85
+ <FormProvider {...form}>
86
+ <BasicForm {...props} methods={form} />
87
+ </FormProvider>
88
+ )
89
+ }
90
+
91
+ export const Form = (props: FormProps) => {
92
+ const useFormMethods = useFormContext()
93
+ const hasFormProvider = !!useFormMethods
94
+
95
+ if (hasFormProvider) {
96
+ return <BasicForm {...props} methods={useFormMethods} />
97
+ }
98
+
99
+ return <FormWithProvider {...props} />
100
+ }
101
+
@@ -0,0 +1,221 @@
1
+ import { Box, Flex, IconBox, Input, Label } from '@citric/core'
2
+ import { SxProperties } from '@citric/core/dist/sx'
3
+ import { InfoCircle, TimesCircle } from '@citric/icons'
4
+ import { FieldAddon, FieldGroup, FormHelper, FormItem, Tooltip } from '@citric/ui'
5
+ import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
6
+ import { debounce, get } from 'lodash'
7
+ import { ChangeEvent, ComponentProps, ComponentPropsWithoutRef, ElementType, ReactElement, ReactNode, useEffect } from 'react'
8
+ import { FieldPath, FieldValues, RegisterOptions, useFormContext, UseFormRegister, UseFormRegisterReturn, UseFormTrigger } from 'react-hook-form'
9
+
10
+ const registerFieldWithDebounceChangeValidation = <T extends FieldValues>(
11
+ name: FieldPath<T>,
12
+ delay: number,
13
+ trigger: UseFormTrigger<T>,
14
+ register: UseFormRegister<T>,
15
+ options?: RegisterOptions<T, FieldPath<T>>,
16
+ ) => {
17
+ const useFormRegisterReturn: UseFormRegisterReturn = register(name, options)
18
+ const { onChange } = useFormRegisterReturn
19
+ const debouncedValidate = debounce(() => {
20
+ trigger(name)
21
+ }, delay)
22
+
23
+ const debouncedChange = debounce((event: ChangeEvent<HTMLInputElement> | ChangeEvent<HTMLSelectElement>) => {
24
+ onChange?.(event)
25
+ }, delay)
26
+ return {
27
+ ...useFormRegisterReturn,
28
+ onChange: (e: any) => {
29
+ debouncedChange(e)
30
+ debouncedValidate()
31
+ },
32
+ }
33
+ }
34
+
35
+ interface FormGroupProps<T extends ElementType> {
36
+ fieldAddonAfter?: ReactNode,
37
+ fieldAddonBefore?: ReactNode,
38
+ formHelper?: ReactNode,
39
+ name: string,
40
+ id?: string,
41
+ component?: T,
42
+ label?: string | ReactElement,
43
+ registerOptions?: RegisterOptions,
44
+ formItemSx?: SxProperties,
45
+ formGroupSx?: SxProperties,
46
+ componentSx?: SxProperties,
47
+ fieldAddonBeforeSx?: SxProperties,
48
+ fieldAddonAfterSx?: SxProperties,
49
+ $formName?: string,
50
+ required?: boolean,
51
+ onChange?: (event: ChangeEvent<HTMLInputElement> | ChangeEvent<HTMLSelectElement>) => void,
52
+ tooltipProps?: Omit<ComponentProps<typeof Tooltip>, 'children'>,
53
+ readOnly?: boolean,
54
+ disabled?: boolean,
55
+ // When true, input will not be registered on form
56
+ standalone?: boolean,
57
+ delay?: number,
58
+ // prop for tests of usability
59
+ hideRequiredSymbol?: boolean,
60
+ shouldUnregister?: boolean,
61
+ }
62
+
63
+ type Props<T extends ElementType> = FormGroupProps<T> & Omit<ComponentPropsWithoutRef<T>, keyof FormGroupProps<T>>
64
+
65
+ export const FormGroup = <T extends ElementType>({
66
+ fieldAddonAfter,
67
+ fieldAddonBefore,
68
+ formHelper,
69
+ id,
70
+ name,
71
+ component,
72
+ label,
73
+ registerOptions = {},
74
+ formGroupSx = {},
75
+ formItemSx = {},
76
+ componentSx = {},
77
+ disabled,
78
+ readOnly,
79
+ onChange,
80
+ tooltipProps,
81
+ required,
82
+ colorScheme: propColorScheme,
83
+ fieldAddonBeforeSx,
84
+ fieldAddonAfterSx,
85
+ standalone,
86
+ delay,
87
+ hideRequiredSymbol,
88
+ shouldUnregister = false,
89
+ ...props
90
+ }: Props<T>) => {
91
+ const t = useTranslate(dictionary)
92
+ const { register, getFieldState, trigger, unregister } = useFormContext()
93
+ const Component = component || Input
94
+ const colorScheme = propColorScheme
95
+
96
+ let formRegistry = {}
97
+
98
+ if (!standalone) {
99
+ const options = {
100
+ onChange,
101
+ required: { message: hideRequiredSymbol ? t.requiredTest : t.required, value: !!required }, validate: (value: string) => {
102
+ if (required && typeof value === 'string') {
103
+ return !!value?.trim()
104
+ }
105
+ },
106
+ ...registerOptions,
107
+ }
108
+
109
+ formRegistry = delay ? registerFieldWithDebounceChangeValidation(name, delay, trigger, register, options) : register(name, options)
110
+ }
111
+
112
+ useEffect(() => () => {
113
+ shouldUnregister && unregister(name)
114
+ }, [name, unregister])
115
+
116
+ return (
117
+ <FormItem sx={formItemSx}>
118
+ {label && (
119
+ <Flex>
120
+ <Label appearance="body2" htmlFor={id || name} sx={{ pointerEvents: disabled || readOnly ? 'none' : undefined }}>
121
+ {label}
122
+ &nbsp;
123
+ {required && !hideRequiredSymbol && '*'}
124
+ </Label>
125
+ {tooltipProps?.text && (
126
+ <Tooltip {...(tooltipProps as typeof tooltipProps & { text: string })}>
127
+ <IconBox>
128
+ <InfoCircle />
129
+ </IconBox>
130
+ </Tooltip>
131
+ )}
132
+ </Flex>
133
+ )}
134
+
135
+ {!fieldAddonBefore && !fieldAddonAfter ? (
136
+ <Component
137
+ id={id || name}
138
+ disabled={disabled}
139
+ readOnly={readOnly}
140
+ {...props}
141
+ {...{ colorScheme }}
142
+ {...formRegistry}
143
+ required={required}
144
+ sx={componentSx}
145
+ aria-errormessage={`err${(id || name)}`}
146
+ aria-invalid={getFieldState(name).invalid}
147
+ />
148
+ ) : (
149
+ <FieldGroup sx={formGroupSx}>
150
+ {fieldAddonBefore && (
151
+ <FieldAddon sx={{ borderColor: colorScheme ? colorScheme : 'light.600', ...fieldAddonBeforeSx }}>
152
+ {fieldAddonBefore}
153
+ </FieldAddon>
154
+ )}
155
+
156
+ <Component
157
+ disabled={disabled}
158
+ readOnly={readOnly}
159
+ id={id || name}
160
+ {...props}
161
+ {...{ colorScheme }}
162
+ {...formRegistry}
163
+ required={required}
164
+ sx={componentSx}
165
+ aria-errormessage={`err${(id || name)}`}
166
+ aria-invalid={getFieldState(name).invalid}
167
+ />
168
+
169
+ {fieldAddonAfter && (
170
+ <FieldAddon sx={{ borderColor: colorScheme ? colorScheme : 'light.600', ...fieldAddonAfterSx }}>
171
+ {fieldAddonAfter}
172
+ </FieldAddon>
173
+ )}
174
+ </FieldGroup>
175
+ )}
176
+ <FormItemError name={name} id={id || name} />
177
+ {formHelper && (
178
+ <Box mt="2">
179
+ <FormHelper>{formHelper}</FormHelper>
180
+ </Box>
181
+ )}
182
+ </FormItem>
183
+ )
184
+ }
185
+
186
+ export const FormItemError = ({ name, id }: { name: string, id: string }) => {
187
+ const t = useTranslate(dictionary)
188
+ const { formState } = useFormContext()
189
+ const formItemError = get(formState.errors, name)
190
+
191
+ if (formItemError) {
192
+ return (
193
+ <Flex alignItems="center" flexWrap="nowrap">
194
+ <IconBox size="xs" appearance="circle" colorIcon="danger" colorBg="danger.500">
195
+ <TimesCircle />
196
+ </IconBox>
197
+ <Box ml="3">
198
+ <FormHelper colorScheme="danger" aria-live="assertive" id={`err${id}`}>
199
+ {formItemError.message?.toString() || formItemError?.root?.message?.toString() || t.invalidGenericError}
200
+ </FormHelper>
201
+ </Box>
202
+ </Flex>
203
+ )
204
+ }
205
+ return null
206
+ }
207
+
208
+ const dictionary = {
209
+ en: {
210
+ invalidGenericError: 'Invalid field',
211
+ required: 'Required field',
212
+ // text for tests of usability
213
+ requiredTest: 'Fill in the required field to continue.',
214
+ },
215
+ pt: {
216
+ invalidGenericError: 'Campo inválido',
217
+ required: 'Campo obrigatório',
218
+ // text for tests of usability
219
+ requiredTest: 'Preencha este campo obrigatório para continuar.',
220
+ },
221
+ } satisfies Dictionary
@@ -0,0 +1,2 @@
1
+ export { FormGroup } from './FormGroup'
2
+ export { Form } from './Form'