@weareconceptstudio/form 0.0.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/README.md +1 -0
- package/dist/checkbox/index.d.ts +3 -0
- package/dist/checkbox/index.js +10 -0
- package/dist/color-picker/index.d.ts +3 -0
- package/dist/color-picker/index.js +8 -0
- package/dist/config-provider/cssVariables.d.ts +2 -0
- package/dist/config-provider/cssVariables.js +25 -0
- package/dist/config-provider/index.d.ts +12 -0
- package/dist/config-provider/index.js +20 -0
- package/dist/date-picker/index.d.ts +3 -0
- package/dist/date-picker/index.js +12 -0
- package/dist/error-message/index.d.ts +9 -0
- package/dist/error-message/index.js +22 -0
- package/dist/form/BaseForm.d.ts +20 -0
- package/dist/form/BaseForm.js +68 -0
- package/dist/form/Item/index.d.ts +11 -0
- package/dist/form/Item/index.js +25 -0
- package/dist/form/hooks/useFormInstance.d.ts +17 -0
- package/dist/form/hooks/useFormInstance.js +6 -0
- package/dist/form/index.d.ts +19 -0
- package/dist/form/index.js +5 -0
- package/dist/form/utils/validators.d.ts +9 -0
- package/dist/form/utils/validators.js +81 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/input/BaseInput/index.d.ts +4 -0
- package/dist/input/BaseInput/index.js +77 -0
- package/dist/input/BaseInput/utils.d.ts +2 -0
- package/dist/input/BaseInput/utils.js +6 -0
- package/dist/input/Input.d.ts +3 -0
- package/dist/input/Input.js +18 -0
- package/dist/input/Number/index.d.ts +3 -0
- package/dist/input/Number/index.js +9 -0
- package/dist/input/Password/icons.d.ts +3 -0
- package/dist/input/Password/icons.js +6 -0
- package/dist/input/Password/index.d.ts +3 -0
- package/dist/input/Password/index.js +59 -0
- package/dist/input/TextArea/index.d.ts +3 -0
- package/dist/input/TextArea/index.js +15 -0
- package/dist/input/index.d.ts +2 -0
- package/dist/input/index.js +8 -0
- package/dist/radio/BaseRadio.d.ts +3 -0
- package/dist/radio/BaseRadio.js +19 -0
- package/dist/radio/Group.d.ts +3 -0
- package/dist/radio/Group.js +20 -0
- package/dist/radio/context.d.ts +6 -0
- package/dist/radio/context.js +6 -0
- package/dist/radio/index.d.ts +2 -0
- package/dist/radio/index.js +5 -0
- package/dist/select/index.d.ts +3 -0
- package/dist/select/index.js +24 -0
- package/dist/upload/index.d.ts +3 -0
- package/dist/upload/index.js +6 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# `Form`
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
const Checkbox = React.forwardRef(({ type = 'checkbox', children }, forwardRef) => {
|
|
3
|
+
return (React.createElement("label", null,
|
|
4
|
+
React.createElement("input", { type: type, ...forwardRef }),
|
|
5
|
+
children !== undefined && React.createElement("span", null, children)));
|
|
6
|
+
});
|
|
7
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
8
|
+
Checkbox.displayName = 'Checkbox';
|
|
9
|
+
}
|
|
10
|
+
export default Checkbox;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
const ColorPicker = React.forwardRef((props, forwardRef) => {
|
|
3
|
+
return (React.createElement("input", { type: 'color', ...forwardRef }));
|
|
4
|
+
});
|
|
5
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
6
|
+
ColorPicker.displayName = 'ColorPicker';
|
|
7
|
+
}
|
|
8
|
+
export default ColorPicker;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { warning, canUseDom, updateCSS } from '@weareconceptstudio/utils';
|
|
2
|
+
export function getStyle(globalPrefixCls = 'cs-form', theme) {
|
|
3
|
+
const variables = {};
|
|
4
|
+
const fillColor = (colorVal, type) => {
|
|
5
|
+
variables[`${type}-color`] = colorVal;
|
|
6
|
+
};
|
|
7
|
+
if (theme.primaryColor) {
|
|
8
|
+
fillColor(theme.primaryColor, 'primary');
|
|
9
|
+
}
|
|
10
|
+
const cssList = Object.keys(variables).map((key) => `--${globalPrefixCls}-${key}: ${variables[key]};`);
|
|
11
|
+
return `
|
|
12
|
+
:root {
|
|
13
|
+
${cssList.join('\n')}
|
|
14
|
+
}
|
|
15
|
+
`.trim();
|
|
16
|
+
}
|
|
17
|
+
export function registerTheme(globalPrefixCls, theme) {
|
|
18
|
+
const style = getStyle(globalPrefixCls, theme);
|
|
19
|
+
if (canUseDom()) {
|
|
20
|
+
updateCSS(style, 'dynamic-theme');
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
warning(false, 'SSR do not support dynamic theme with css variables.');
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export declare const ConfigContext: React.Context<{}>;
|
|
3
|
+
interface ConfigProviderProps {
|
|
4
|
+
globalPrefixCls?: string;
|
|
5
|
+
theme?: object;
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
}
|
|
8
|
+
declare const ConfigProvider: {
|
|
9
|
+
(props: ConfigProviderProps): React.JSX.Element;
|
|
10
|
+
displayName: string;
|
|
11
|
+
};
|
|
12
|
+
export default ConfigProvider;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { registerTheme } from './cssVariables';
|
|
3
|
+
export const ConfigContext = React.createContext({});
|
|
4
|
+
const ProviderChildren = (props) => {
|
|
5
|
+
const { globalPrefixCls = 'cs-form', theme, children } = props;
|
|
6
|
+
if (theme) {
|
|
7
|
+
registerTheme(globalPrefixCls, theme);
|
|
8
|
+
}
|
|
9
|
+
return (React.createElement(ConfigContext.Provider, { value: {
|
|
10
|
+
globalPrefixCls,
|
|
11
|
+
theme
|
|
12
|
+
} }, children));
|
|
13
|
+
};
|
|
14
|
+
const ConfigProvider = (props) => {
|
|
15
|
+
return React.createElement(ProviderChildren, { ...props });
|
|
16
|
+
};
|
|
17
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
18
|
+
ConfigProvider.displayName = 'ConfigProvider';
|
|
19
|
+
}
|
|
20
|
+
export default ConfigProvider;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
/*
|
|
3
|
+
types - default=date,datetime-local,month,time,week
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
const DatePicker = React.forwardRef(({ type = 'date' }, forwardRef) => {
|
|
7
|
+
return (React.createElement("input", { type: type, ...forwardRef }));
|
|
8
|
+
});
|
|
9
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
10
|
+
DatePicker.displayName = 'DatePicker';
|
|
11
|
+
}
|
|
12
|
+
export default DatePicker;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useFormContext, get } from 'react-hook-form';
|
|
3
|
+
const ErrorMessage = ({ as, errors, name, message, render, ...rest }) => {
|
|
4
|
+
const methods = useFormContext();
|
|
5
|
+
const error = get(errors || methods.formState.errors, name);
|
|
6
|
+
if (!error) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
const { message: messageFromRegister, types } = error;
|
|
10
|
+
const props = Object.assign({}, rest, {
|
|
11
|
+
children: messageFromRegister || message,
|
|
12
|
+
});
|
|
13
|
+
return React.isValidElement(as)
|
|
14
|
+
? React.cloneElement(as, props)
|
|
15
|
+
: render
|
|
16
|
+
? (render({
|
|
17
|
+
message: messageFromRegister || message,
|
|
18
|
+
messages: types,
|
|
19
|
+
}))
|
|
20
|
+
: React.createElement(as || React.Fragment, props);
|
|
21
|
+
};
|
|
22
|
+
export default ErrorMessage;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export default BaseForm;
|
|
2
|
+
declare function BaseForm({ initialValues: defaultValues, values, children, onSubmit, layout, mode, reValidateMode, errors, resetOptions, criteriaMode, shouldFocusError, delayError, shouldUseNativeValidation, shouldUnregister, globalPrefixCls, theme, }: {
|
|
3
|
+
initialValues: any;
|
|
4
|
+
values: any;
|
|
5
|
+
children: any;
|
|
6
|
+
onSubmit: any;
|
|
7
|
+
layout?: string;
|
|
8
|
+
mode?: string;
|
|
9
|
+
reValidateMode: any;
|
|
10
|
+
errors: any;
|
|
11
|
+
resetOptions: any;
|
|
12
|
+
criteriaMode: any;
|
|
13
|
+
shouldFocusError: any;
|
|
14
|
+
delayError: any;
|
|
15
|
+
shouldUseNativeValidation: any;
|
|
16
|
+
shouldUnregister: any;
|
|
17
|
+
globalPrefixCls: any;
|
|
18
|
+
theme: any;
|
|
19
|
+
}): React.JSX.Element;
|
|
20
|
+
import * as React from 'react';
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { FormProvider, useForm } from "react-hook-form";
|
|
3
|
+
import styled from 'styled-components';
|
|
4
|
+
import ConfigProvider from '../config-provider';
|
|
5
|
+
// initialValues: object || async () => fetch('/api-endpoint')
|
|
6
|
+
// values: object
|
|
7
|
+
// children: React elements
|
|
8
|
+
// onSubmit: func (values) => {}
|
|
9
|
+
// layout: vertical | horizontal
|
|
10
|
+
// mode: onChange | onBlur | onSubmit | onTouched | all = 'onSubmit'
|
|
11
|
+
// reValidateMode: onChange | onBlur | onSubmit = 'onChange'
|
|
12
|
+
// errors: Reactive errors to update the form errors.
|
|
13
|
+
// resetOptions: Option to reset form state update while updating new form values.
|
|
14
|
+
// criteriaMode: Display all validation errors or one at a time. (firstError | all)
|
|
15
|
+
// shouldFocusError: Enable or disable built-in focus management.
|
|
16
|
+
// delayError: Delay error from appearing instantly.
|
|
17
|
+
// shouldUseNativeValidation: Use browser built-in form constraint API.
|
|
18
|
+
// shouldUnregister: Enable and disable input unregister after unmount.
|
|
19
|
+
/**
|
|
20
|
+
* defaultValues are cached. To reset them, use the reset API.
|
|
21
|
+
* defaultValues will be included in the submission result by default.
|
|
22
|
+
* values will get updated when values props updates
|
|
23
|
+
* resetOptions: {
|
|
24
|
+
keepDirtyValues: true, // user-interacted input will be retained
|
|
25
|
+
keepErrors: true, // input errors will be retained with value update
|
|
26
|
+
},
|
|
27
|
+
* criteriaMode:{
|
|
28
|
+
When set to firstError (default), only the first error from each field will be gathered.
|
|
29
|
+
When set to all, all errors from each field will be gathered.
|
|
30
|
+
}
|
|
31
|
+
* shouldFocusError: When set to true (default), and the user submits a form that fails validation, focus is set on the first field with an error.
|
|
32
|
+
* delayError: number
|
|
33
|
+
*
|
|
34
|
+
*/
|
|
35
|
+
const StyledForm = styled.form `
|
|
36
|
+
&.vertical {
|
|
37
|
+
.form-item-row {
|
|
38
|
+
flex-direction: column;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.form-item-row {
|
|
43
|
+
display: flex;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.form-item {
|
|
47
|
+
margin-bottom: 24px;
|
|
48
|
+
}
|
|
49
|
+
`;
|
|
50
|
+
const BaseForm = ({ initialValues: defaultValues, values, children, onSubmit, layout = 'vertical', mode = 'onChange', reValidateMode, errors, resetOptions, criteriaMode, shouldFocusError, delayError, shouldUseNativeValidation, shouldUnregister, globalPrefixCls, theme, }) => {
|
|
51
|
+
const methods = useForm({
|
|
52
|
+
defaultValues,
|
|
53
|
+
values,
|
|
54
|
+
mode,
|
|
55
|
+
reValidateMode,
|
|
56
|
+
errors,
|
|
57
|
+
resetOptions,
|
|
58
|
+
criteriaMode,
|
|
59
|
+
shouldFocusError,
|
|
60
|
+
delayError,
|
|
61
|
+
shouldUseNativeValidation,
|
|
62
|
+
shouldUnregister,
|
|
63
|
+
});
|
|
64
|
+
return (React.createElement(ConfigProvider, { globalPrefixCls: globalPrefixCls, theme: theme },
|
|
65
|
+
React.createElement(FormProvider, { ...methods },
|
|
66
|
+
React.createElement(StyledForm, { onSubmit: methods.handleSubmit(onSubmit), className: layout }, children))));
|
|
67
|
+
};
|
|
68
|
+
export default BaseForm;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export default FormItem;
|
|
2
|
+
declare function FormItem({ label, name, children, rules }: {
|
|
3
|
+
label: any;
|
|
4
|
+
name: any;
|
|
5
|
+
children: any;
|
|
6
|
+
rules?: {};
|
|
7
|
+
}): React.JSX.Element;
|
|
8
|
+
declare namespace FormItem {
|
|
9
|
+
let displayName: string;
|
|
10
|
+
}
|
|
11
|
+
import React from 'react';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
//* HOC's
|
|
3
|
+
import { useFormContext } from "react-hook-form";
|
|
4
|
+
//* Components
|
|
5
|
+
import ErrorMessage from '../../error-message';
|
|
6
|
+
const FormItem = ({ label, name, children, rules = {} }) => {
|
|
7
|
+
const { register, getValues, setValue } = useFormContext();
|
|
8
|
+
const innerRef = register(name, rules);
|
|
9
|
+
return (React.createElement("div", { className: "form-item" },
|
|
10
|
+
React.createElement("div", { className: "form-item-row" },
|
|
11
|
+
React.createElement("div", { className: "form-item-label" },
|
|
12
|
+
React.createElement("label", { htmlFor: name }, label)),
|
|
13
|
+
React.createElement("div", { className: 'form-item-control' }, React.cloneElement(children, {
|
|
14
|
+
ref: innerRef,
|
|
15
|
+
getValues,
|
|
16
|
+
setValue,
|
|
17
|
+
name,
|
|
18
|
+
value: getValues()[name]
|
|
19
|
+
})),
|
|
20
|
+
React.createElement(ErrorMessage, { name: name, as: 'span' }))));
|
|
21
|
+
};
|
|
22
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
23
|
+
FormItem.displayName = 'FormItem';
|
|
24
|
+
}
|
|
25
|
+
export default FormItem;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export default function useFormInstance(): {
|
|
2
|
+
watch: import("react-hook-form").UseFormWatch<import("react-hook-form").FieldValues>;
|
|
3
|
+
getValues: import("react-hook-form").UseFormGetValues<import("react-hook-form").FieldValues>;
|
|
4
|
+
getFieldState: import("react-hook-form").UseFormGetFieldState<import("react-hook-form").FieldValues>;
|
|
5
|
+
setError: import("react-hook-form").UseFormSetError<import("react-hook-form").FieldValues>;
|
|
6
|
+
clearErrors: import("react-hook-form").UseFormClearErrors<import("react-hook-form").FieldValues>;
|
|
7
|
+
setValue: import("react-hook-form").UseFormSetValue<import("react-hook-form").FieldValues>;
|
|
8
|
+
trigger: import("react-hook-form").UseFormTrigger<import("react-hook-form").FieldValues>;
|
|
9
|
+
formState: import("react-hook-form").FormState<import("react-hook-form").FieldValues>;
|
|
10
|
+
resetField: import("react-hook-form").UseFormResetField<import("react-hook-form").FieldValues>;
|
|
11
|
+
reset: import("react-hook-form").UseFormReset<import("react-hook-form").FieldValues>;
|
|
12
|
+
handleSubmit: import("react-hook-form").UseFormHandleSubmit<import("react-hook-form").FieldValues, undefined>;
|
|
13
|
+
unregister: import("react-hook-form").UseFormUnregister<import("react-hook-form").FieldValues>;
|
|
14
|
+
control: import("react-hook-form").Control<import("react-hook-form").FieldValues, any>;
|
|
15
|
+
register: import("react-hook-form").UseFormRegister<import("react-hook-form").FieldValues>;
|
|
16
|
+
setFocus: import("react-hook-form").UseFormSetFocus<import("react-hook-form").FieldValues>;
|
|
17
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export default Form;
|
|
2
|
+
declare const Form: ({ initialValues: defaultValues, values, children, onSubmit, layout, mode, reValidateMode, errors, resetOptions, criteriaMode, shouldFocusError, delayError, shouldUseNativeValidation, shouldUnregister, globalPrefixCls, theme, }: {
|
|
3
|
+
initialValues: any;
|
|
4
|
+
values: any;
|
|
5
|
+
children: any;
|
|
6
|
+
onSubmit: any;
|
|
7
|
+
layout?: string;
|
|
8
|
+
mode?: string;
|
|
9
|
+
reValidateMode: any;
|
|
10
|
+
errors: any;
|
|
11
|
+
resetOptions: any;
|
|
12
|
+
criteriaMode: any;
|
|
13
|
+
shouldFocusError: any;
|
|
14
|
+
delayError: any;
|
|
15
|
+
shouldUseNativeValidation: any;
|
|
16
|
+
shouldUnregister: any;
|
|
17
|
+
globalPrefixCls: any;
|
|
18
|
+
theme: any;
|
|
19
|
+
}) => import("react").JSX.Element;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function hasLength(payload: any, error: any): (value: any) => any;
|
|
2
|
+
export function isEmail(error: any): (value: any) => any;
|
|
3
|
+
export function isInRange({ min, max }: {
|
|
4
|
+
min: any;
|
|
5
|
+
max: any;
|
|
6
|
+
}, error: any): (value: any) => any;
|
|
7
|
+
export function isNotEmpty(error: any): (value: any) => any;
|
|
8
|
+
export function matchesField(field: any, error: any): (value: any, values: any) => any;
|
|
9
|
+
export function matches(regexp: any, error: any): (value: any) => any;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
function isLengthValid(payload, value) {
|
|
2
|
+
if (typeof payload === 'number') {
|
|
3
|
+
return value.length === payload;
|
|
4
|
+
}
|
|
5
|
+
const { max, min } = payload;
|
|
6
|
+
let valid = true;
|
|
7
|
+
if (typeof max === 'number' && value.length > max) {
|
|
8
|
+
valid = false;
|
|
9
|
+
}
|
|
10
|
+
if (typeof min === 'number' && value.length < min) {
|
|
11
|
+
valid = false;
|
|
12
|
+
}
|
|
13
|
+
return valid;
|
|
14
|
+
}
|
|
15
|
+
export function hasLength(payload, error) {
|
|
16
|
+
const _error = error || true;
|
|
17
|
+
return (value) => {
|
|
18
|
+
if (typeof value === 'string') {
|
|
19
|
+
return isLengthValid(payload, value.trim()) ? null : _error;
|
|
20
|
+
}
|
|
21
|
+
if (typeof value === 'object' && value !== null && 'length' in value) {
|
|
22
|
+
return isLengthValid(payload, value) ? null : _error;
|
|
23
|
+
}
|
|
24
|
+
return _error;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export function isEmail(error) {
|
|
28
|
+
return matches(/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/, error);
|
|
29
|
+
}
|
|
30
|
+
export function isInRange({ min, max }, error) {
|
|
31
|
+
const _error = error || true;
|
|
32
|
+
return (value) => {
|
|
33
|
+
if (typeof value !== 'number') {
|
|
34
|
+
return _error;
|
|
35
|
+
}
|
|
36
|
+
let valid = true;
|
|
37
|
+
if (typeof min === 'number' && value < min) {
|
|
38
|
+
valid = false;
|
|
39
|
+
}
|
|
40
|
+
if (typeof max === 'number' && value > max) {
|
|
41
|
+
valid = false;
|
|
42
|
+
}
|
|
43
|
+
return valid ? null : _error;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export function isNotEmpty(error) {
|
|
47
|
+
const _error = error || true;
|
|
48
|
+
return (value) => {
|
|
49
|
+
if (typeof value === 'string') {
|
|
50
|
+
return value.trim().length > 0 ? null : _error;
|
|
51
|
+
}
|
|
52
|
+
if (Array.isArray(value)) {
|
|
53
|
+
return value.length > 0 ? null : _error;
|
|
54
|
+
}
|
|
55
|
+
if (value === null || value === undefined) {
|
|
56
|
+
return _error;
|
|
57
|
+
}
|
|
58
|
+
if (value === false) {
|
|
59
|
+
return _error;
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export function matchesField(field, error) {
|
|
65
|
+
const _error = error || true;
|
|
66
|
+
return (value, values) => {
|
|
67
|
+
if (!values || !(field in values)) {
|
|
68
|
+
return _error;
|
|
69
|
+
}
|
|
70
|
+
return value === values[field] ? null : _error;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export function matches(regexp, error) {
|
|
74
|
+
const _error = error || true;
|
|
75
|
+
return (value) => {
|
|
76
|
+
if (typeof value !== 'string') {
|
|
77
|
+
return _error;
|
|
78
|
+
}
|
|
79
|
+
return regexp.test(value) ? null : _error;
|
|
80
|
+
};
|
|
81
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { default as Form } from "./form";
|
|
2
|
+
export { default as ErrorMessage } from "./error-message";
|
|
3
|
+
export { default as Input } from "./input";
|
|
4
|
+
export { default as Checkbox } from "./checkbox";
|
|
5
|
+
export { default as ColorPicker } from "./color-picker";
|
|
6
|
+
export { default as DatePicker } from "./date-picker";
|
|
7
|
+
export { default as Radio } from "./radio";
|
|
8
|
+
export { default as Select } from "./select";
|
|
9
|
+
export { default as Upload } from "./upload";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { default as Form } from './form';
|
|
2
|
+
export { default as ErrorMessage } from './error-message';
|
|
3
|
+
export { default as Input } from './input';
|
|
4
|
+
export { default as Checkbox } from './checkbox';
|
|
5
|
+
export { default as ColorPicker } from './color-picker';
|
|
6
|
+
export { default as DatePicker } from './date-picker';
|
|
7
|
+
export { default as Radio } from './radio';
|
|
8
|
+
export { default as Select } from './select';
|
|
9
|
+
export { default as Upload } from './upload';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export const InputContainer: import("styled-components").IStyledComponent<"web", import("styled-components/dist/types").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>>;
|
|
2
|
+
export default BaseInput;
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
declare const BaseInput: React.ForwardRefExoticComponent<React.RefAttributes<any>>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
import clsx from 'classnames';
|
|
4
|
+
import { hasPrefixSuffix, hasAddon } from './utils';
|
|
5
|
+
export const InputContainer = styled.div `
|
|
6
|
+
.affix-wrapper {
|
|
7
|
+
position:relative;
|
|
8
|
+
display:inline-flex;
|
|
9
|
+
|
|
10
|
+
.clear-icon {
|
|
11
|
+
font-size: 12px;
|
|
12
|
+
cursor: pointer;
|
|
13
|
+
|
|
14
|
+
&-hidden {
|
|
15
|
+
display: none;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
`;
|
|
20
|
+
const BaseInput = React.forwardRef((props, forwardRef) => {
|
|
21
|
+
const { inputElement: inputEl, addonBefore, addonAfter, prefix, suffix, allowClear, disabled, readOnly, components, value, className, children } = props;
|
|
22
|
+
const inputElement = children ?? inputEl;
|
|
23
|
+
// ======================== Components ======================== //
|
|
24
|
+
const AffixWrapperComponent = components?.affixWrapper || 'span';
|
|
25
|
+
const GroupWrapperComponent = components?.groupWrapper || 'span';
|
|
26
|
+
const WrapperComponent = components?.wrapper || 'span';
|
|
27
|
+
const GroupAddonComponent = components?.groupAddon || 'span';
|
|
28
|
+
// ======================== Refs ======================== //
|
|
29
|
+
const containerRef = React.useRef(null);
|
|
30
|
+
const groupRef = React.useRef(null);
|
|
31
|
+
let element = React.cloneElement(inputElement, {
|
|
32
|
+
// ...props,
|
|
33
|
+
className: `base-input`
|
|
34
|
+
});
|
|
35
|
+
// ======================== Prefix && Suffix ======================== //
|
|
36
|
+
if (hasPrefixSuffix(props)) {
|
|
37
|
+
let clearIcon = null;
|
|
38
|
+
if (allowClear) {
|
|
39
|
+
const needClear = !disabled && !readOnly && !!value;
|
|
40
|
+
const clearIconCls = 'clear-icon';
|
|
41
|
+
const iconNode = typeof allowClear === 'object' && allowClear?.clearIcon
|
|
42
|
+
? allowClear.clearIcon
|
|
43
|
+
: '✖';
|
|
44
|
+
clearIcon = (React.createElement("span", {
|
|
45
|
+
// onClick={(event) => {
|
|
46
|
+
// handleReset?.(event);
|
|
47
|
+
// onClear?.();
|
|
48
|
+
// }}
|
|
49
|
+
onMouseDown: (e) => e.preventDefault(), role: "button", tabIndex: -1, className: clsx(clearIconCls, {
|
|
50
|
+
[`${clearIconCls}-hidden`]: !needClear,
|
|
51
|
+
[`${clearIconCls}-has-suffix`]: !!suffix,
|
|
52
|
+
}) }, iconNode));
|
|
53
|
+
}
|
|
54
|
+
const suffixNode = (suffix || allowClear) && (React.createElement("span", null,
|
|
55
|
+
clearIcon,
|
|
56
|
+
suffix));
|
|
57
|
+
element = (React.createElement(AffixWrapperComponent, { ref: containerRef, className: 'affix-wrapper' },
|
|
58
|
+
prefix && (React.createElement("span", null, prefix)),
|
|
59
|
+
element,
|
|
60
|
+
suffixNode));
|
|
61
|
+
}
|
|
62
|
+
// ======================== Addon (Before && After) ======================== //
|
|
63
|
+
if (hasAddon(props)) {
|
|
64
|
+
element = (React.createElement(GroupWrapperComponent, { ref: groupRef },
|
|
65
|
+
React.createElement(WrapperComponent, null,
|
|
66
|
+
addonBefore && (React.createElement(GroupAddonComponent, null, addonBefore)),
|
|
67
|
+
element,
|
|
68
|
+
addonAfter && (React.createElement(GroupAddonComponent, null, addonAfter)))));
|
|
69
|
+
}
|
|
70
|
+
return (React.createElement(InputContainer, null, React.cloneElement(element, {
|
|
71
|
+
className: clsx(element.props?.className, className) || null,
|
|
72
|
+
})));
|
|
73
|
+
});
|
|
74
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
75
|
+
BaseInput.displayName = 'BaseInput';
|
|
76
|
+
}
|
|
77
|
+
export default BaseInput;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
import BaseInput from './BaseInput';
|
|
4
|
+
const StyledInput = styled.input `
|
|
5
|
+
box-sizing: border-box;
|
|
6
|
+
margin: 0;
|
|
7
|
+
list-style: none;
|
|
8
|
+
display: inline-block;
|
|
9
|
+
font-size: 14px;
|
|
10
|
+
padding: 4px 11px;
|
|
11
|
+
outline: none;
|
|
12
|
+
`;
|
|
13
|
+
const Input = React.forwardRef((props, forwardRef) => {
|
|
14
|
+
const { autoComplete, onChange, onFocus, onBlur, onPressEnter, onKeyDown, onKeyUp, disabled, className, type = 'text', value, } = props;
|
|
15
|
+
return (React.createElement(BaseInput, { ...props, value: value, disabled: disabled },
|
|
16
|
+
React.createElement(StyledInput, { type: type, autoComplete: autoComplete, onChange: onChange, onFocus: onFocus, onBlur: onBlur, onPressEnter: onPressEnter, onKeyDown: onKeyDown, onKeyUp: onKeyUp, disabled: disabled, className: className, ...forwardRef })));
|
|
17
|
+
});
|
|
18
|
+
export default Input;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import Input from '../Input';
|
|
3
|
+
const Number = React.forwardRef((props, forwardRef) => {
|
|
4
|
+
return (React.createElement(Input, { ref: forwardRef, type: 'number', ...props }));
|
|
5
|
+
});
|
|
6
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
7
|
+
Number.displayName = 'InputNumber';
|
|
8
|
+
}
|
|
9
|
+
export default Number;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export const eyeIcon = React.createElement("svg", { viewBox: "64 64 896 896", focusable: "false", "data-icon": "eye-invisible", width: "1em", height: "1em", fill: "currentColor", "aria-hidden": "true" },
|
|
3
|
+
React.createElement("path", { d: "M942.2 486.2Q889.47 375.11 816.7 305l-50.88 50.88C807.31 395.53 843.45 447.4 874.7 512 791.5 684.2 673.4 766 512 766q-72.67 0-133.87-22.38L323 798.75Q408 838 512 838q288.3 0 430.2-300.3a60.29 60.29 0 000-51.5zm-63.57-320.64L836 122.88a8 8 0 00-11.32 0L715.31 232.2Q624.86 186 512 186q-288.3 0-430.2 300.3a60.3 60.3 0 000 51.5q56.69 119.4 136.5 191.41L112.48 835a8 8 0 000 11.31L155.17 889a8 8 0 0011.31 0l712.15-712.12a8 8 0 000-11.32zM149.3 512C232.6 339.8 350.7 258 512 258c54.54 0 104.13 9.36 149.12 28.39l-70.3 70.3a176 176 0 00-238.13 238.13l-83.42 83.42C223.1 637.49 183.3 582.28 149.3 512zm246.7 0a112.11 112.11 0 01146.2-106.69L401.31 546.2A112 112 0 01396 512z" }),
|
|
4
|
+
React.createElement("path", { d: "M508 624c-3.46 0-6.87-.16-10.25-.47l-52.82 52.82a176.09 176.09 0 00227.42-227.42l-52.82 52.82c.31 3.38.47 6.79.47 10.25a111.94 111.94 0 01-112 112z" }));
|
|
5
|
+
export const openEye = React.createElement("svg", { viewBox: "64 64 896 896", focusable: "false", "data-icon": "eye", width: "1em", height: "1em", fill: "currentColor", "aria-hidden": "true" },
|
|
6
|
+
React.createElement("path", { d: "M942.2 486.2C847.4 286.5 704.1 186 512 186c-192.2 0-335.4 100.5-430.2 300.3a60.3 60.3 0 000 51.5C176.6 737.5 319.9 838 512 838c192.2 0 335.4-100.5 430.2-300.3 7.7-16.2 7.7-35 0-51.5zM512 766c-161.3 0-279.4-81.8-362.7-254C232.6 339.8 350.7 258 512 258c161.3 0 279.4 81.8 362.7 254C791.5 684.2 673.4 766 512 766zm-4-430c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm0 288c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z" }));
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { omit } from '@weareconceptstudio/utils';
|
|
3
|
+
import { eyeIcon, openEye } from './icons';
|
|
4
|
+
import Input from '../Input';
|
|
5
|
+
const defaultIconRender = (visible) => visible ? openEye : eyeIcon;
|
|
6
|
+
const actionMap = {
|
|
7
|
+
click: 'onClick',
|
|
8
|
+
hover: 'onMouseOver',
|
|
9
|
+
};
|
|
10
|
+
const Password = React.forwardRef((props, ref) => {
|
|
11
|
+
const { disabled, action = 'click', visibilityToggle = true, iconRender = defaultIconRender } = props;
|
|
12
|
+
const visibilityControlled = typeof visibilityToggle === 'object' && visibilityToggle.visible !== undefined;
|
|
13
|
+
const [visible, setVisible] = React.useState(() => visibilityControlled ? visibilityToggle.visible : false);
|
|
14
|
+
React.useEffect(() => {
|
|
15
|
+
if (visibilityControlled) {
|
|
16
|
+
setVisible(visibilityToggle.visible);
|
|
17
|
+
}
|
|
18
|
+
}, [visibilityControlled, visibilityToggle]);
|
|
19
|
+
const onVisibleChange = () => {
|
|
20
|
+
if (disabled) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
setVisible((prevState) => {
|
|
24
|
+
const newState = !prevState;
|
|
25
|
+
if (typeof visibilityToggle === 'object') {
|
|
26
|
+
visibilityToggle.onVisibleChange?.(newState);
|
|
27
|
+
}
|
|
28
|
+
return newState;
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
const getIcon = () => {
|
|
32
|
+
const iconTrigger = actionMap[action] || '';
|
|
33
|
+
const icon = iconRender(visible);
|
|
34
|
+
const iconProps = {
|
|
35
|
+
[iconTrigger]: onVisibleChange,
|
|
36
|
+
className: 'input-password-icon',
|
|
37
|
+
key: 'passwordIcon',
|
|
38
|
+
onMouseDown: (e) => {
|
|
39
|
+
e.preventDefault();
|
|
40
|
+
},
|
|
41
|
+
onMouseUp: (e) => {
|
|
42
|
+
e.preventDefault();
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
return React.cloneElement(React.isValidElement(icon) ? icon : React.createElement("span", null, icon), iconProps);
|
|
46
|
+
};
|
|
47
|
+
const suffixIcon = visibilityToggle && getIcon();
|
|
48
|
+
const { className, ...restProps } = props;
|
|
49
|
+
const omittedProps = {
|
|
50
|
+
...omit(restProps, ['suffix', 'iconRender', 'visibilityToggle']),
|
|
51
|
+
type: visible ? 'text' : 'password',
|
|
52
|
+
suffix: suffixIcon,
|
|
53
|
+
};
|
|
54
|
+
return (React.createElement(Input, { ref: ref, ...omittedProps }));
|
|
55
|
+
});
|
|
56
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
57
|
+
Password.displayName = 'InputPassword';
|
|
58
|
+
}
|
|
59
|
+
export default Password;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import styled from 'styled-components';
|
|
3
|
+
import BaseInput from '../BaseInput';
|
|
4
|
+
const StyledTextArea = styled.textarea `
|
|
5
|
+
font-size: 14px;
|
|
6
|
+
padding: 4px 11px;
|
|
7
|
+
`;
|
|
8
|
+
const TextArea = React.forwardRef((props, forwardRef) => {
|
|
9
|
+
return (React.createElement(BaseInput, { ...props },
|
|
10
|
+
React.createElement(StyledTextArea, { ...forwardRef, rows: 5 })));
|
|
11
|
+
});
|
|
12
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
13
|
+
TextArea.displayName = 'TextArea';
|
|
14
|
+
}
|
|
15
|
+
export default TextArea;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import RadioGroupContext, { RadioOptionTypeContext } from './context';
|
|
3
|
+
import Checkbox from "../checkbox";
|
|
4
|
+
const Radio = React.forwardRef(({ children, ...restProps }, forwardRef) => {
|
|
5
|
+
const groupContext = React.useContext(RadioGroupContext);
|
|
6
|
+
const radioProps = {
|
|
7
|
+
ref: forwardRef,
|
|
8
|
+
};
|
|
9
|
+
if (groupContext) {
|
|
10
|
+
radioProps.ref = groupContext.forwardRef;
|
|
11
|
+
}
|
|
12
|
+
return (React.createElement("label", null,
|
|
13
|
+
React.createElement(Checkbox, { type: 'radio', ...radioProps }),
|
|
14
|
+
children !== undefined ? React.createElement("span", null, children) : null));
|
|
15
|
+
});
|
|
16
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
17
|
+
Radio.displayName = 'Radio';
|
|
18
|
+
}
|
|
19
|
+
export default Radio;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { RadioGroupContextProvider } from './context';
|
|
3
|
+
import Radio from './BaseRadio';
|
|
4
|
+
const RadioGroup = React.forwardRef((props, forwardRef) => {
|
|
5
|
+
const { children, options } = props;
|
|
6
|
+
let childrenToRender = children;
|
|
7
|
+
if (options && options.length > 0) {
|
|
8
|
+
childrenToRender = options.map(option => {
|
|
9
|
+
if (typeof option === 'string' || typeof option === 'number') {
|
|
10
|
+
return (React.createElement(Radio, { key: option.toString(), value: option }, option));
|
|
11
|
+
}
|
|
12
|
+
return (React.createElement(Radio, { key: `radio-group-value-options-${option.value}`, value: option.value }, option.label));
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
return (React.createElement("div", null,
|
|
16
|
+
React.createElement(RadioGroupContextProvider, { value: {
|
|
17
|
+
forwardRef: forwardRef,
|
|
18
|
+
} }, childrenToRender)));
|
|
19
|
+
});
|
|
20
|
+
export default RadioGroup;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export const RadioGroupContextProvider: React.Provider<any>;
|
|
2
|
+
export default RadioGroupContext;
|
|
3
|
+
export const RadioOptionTypeContext: React.Context<any>;
|
|
4
|
+
export const RadioOptionTypeContextProvider: React.Provider<any>;
|
|
5
|
+
import * as React from 'react';
|
|
6
|
+
declare const RadioGroupContext: React.Context<any>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
const RadioGroupContext = React.createContext(null);
|
|
3
|
+
export const RadioGroupContextProvider = RadioGroupContext.Provider;
|
|
4
|
+
export default RadioGroupContext;
|
|
5
|
+
export const RadioOptionTypeContext = React.createContext(null);
|
|
6
|
+
export const RadioOptionTypeContextProvider = RadioOptionTypeContext.Provider;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import ReactSelect from 'react-select';
|
|
3
|
+
const Select = React.forwardRef((props, forwardRef) => {
|
|
4
|
+
const { name, getValues, setValue, multiple = false, allowClear = false, allowSearch = true, disabled = false, loading = false, options = [], } = props;
|
|
5
|
+
const [val, setVal] = React.useState(getValues()[name] || (multiple ? [] : ''));
|
|
6
|
+
const handleChange = React.useCallback((newVal) => {
|
|
7
|
+
let vals;
|
|
8
|
+
if (multiple) {
|
|
9
|
+
vals = newVal.map(item => item.value);
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
vals = newVal.value;
|
|
13
|
+
}
|
|
14
|
+
setValue(name, vals);
|
|
15
|
+
setVal(vals);
|
|
16
|
+
}, [name, multiple]);
|
|
17
|
+
return (React.createElement("div", null,
|
|
18
|
+
React.createElement("input", { type: 'hidden', ...forwardRef }),
|
|
19
|
+
React.createElement(ReactSelect, { options: options, onChange: handleChange, value: options.filter((opt) => multiple ? val.includes(opt.value) : opt.value === val), isMulti: multiple, isClearable: allowClear, isSearchable: allowSearch, isDisabled: disabled, isLoading: loading })));
|
|
20
|
+
});
|
|
21
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
22
|
+
Select.displayName = 'Select';
|
|
23
|
+
}
|
|
24
|
+
export default Select;
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@weareconceptstudio/form",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Concept Studio Form",
|
|
5
|
+
"author": "Concept Studio",
|
|
6
|
+
"license": "ISC",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc -m es6",
|
|
19
|
+
"watch": "tsc -m es6 --watch"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@weareconceptstudio/utils": "*",
|
|
23
|
+
"classnames": "2.5.1",
|
|
24
|
+
"react": "^18.3.1",
|
|
25
|
+
"react-dom": "^18.3.1",
|
|
26
|
+
"react-hook-form": "^7.52.1",
|
|
27
|
+
"react-select": "5.8.0",
|
|
28
|
+
"styled-components": "^6.1.8"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@babel/plugin-proposal-private-property-in-object": "7.21.11",
|
|
32
|
+
"rimraf": "5.0.5",
|
|
33
|
+
"typescript": "5.3.3"
|
|
34
|
+
}
|
|
35
|
+
}
|