From 0caf609dca61d86ddb7b4c2e24f82cf88bdef4bb Mon Sep 17 00:00:00 2001 From: Prabhu-Kathiresan Date: Thu, 24 Feb 2022 21:50:44 +0530 Subject: [PATCH] fix: minor improvements --- package-lock.json | 57 ++++- package.json | 2 +- src/ButtonGroup/index.tsx | 32 ++- src/ButtonGroup/props.ts | 2 +- src/DatePicker/DatePickerElement.tsx | 4 +- src/DatePicker/DatePickerHeader.tsx | 5 +- src/DatePicker/index.tsx | 18 +- src/DatePicker/props.ts | 4 +- src/Dialog/Dialog.tsx | 2 +- src/Form/Form.tsx | 100 +++++++-- src/Form/form-data.ts | 85 ++++--- src/Form/props.ts | 71 +++++- src/Form/schema.ts | 319 ++++++++++++++------------- src/Form/utils.ts | 16 ++ src/MonthPicker/index.tsx | 3 + src/MonthPicker/props.ts | 1 + src/Radio/RadioGroup.tsx | 8 +- src/Radio/props.ts | 2 + src/Select/BasicSelect.tsx | 116 +++++----- src/Select/InputWrapper.tsx | 46 ++-- src/TextInput/index.tsx | 6 +- src/scss/_variables.scss | 1 + src/scss/accordion.scss | 2 +- src/scss/alert.scss | 12 + src/scss/common.scss | 23 ++ src/scss/dialog.scss | 5 + src/scss/select.scss | 2 +- 27 files changed, 618 insertions(+), 326 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1ae858c..64d6f2c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5931,6 +5931,11 @@ "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", "dev": true }, + "nanoclone": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz", + "integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==" + }, "nanoid": { "version": "3.1.30", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz", @@ -8912,6 +8917,29 @@ "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" + }, + "dependencies": { + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + } } }, "react-dom": { @@ -10884,15 +10912,32 @@ } }, "yup": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/yup/-/yup-0.30.0.tgz", - "integrity": "sha512-GX3vqpC9E+Ow0fmQPgqbEg7UV40XRrN1IOEgKF5v04v6T4ha2vBas/hu0thWgewk8L4wUEBLRO/EnXwYyP+p+A==", + "version": "0.32.11", + "resolved": "https://registry.npmjs.org/yup/-/yup-0.32.11.tgz", + "integrity": "sha512-Z2Fe1bn+eLstG8DRR6FTavGD+MeAwyfmouhHsIUgaADz8jvFKbO/fXc2trJKZg+5EBjh4gGm3iU/t3onKlXHIg==", "requires": { - "@babel/runtime": "^7.10.5", - "lodash": "^4.17.20", - "lodash-es": "^4.17.11", + "@babel/runtime": "^7.15.4", + "@types/lodash": "^4.14.175", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "nanoclone": "^0.2.1", "property-expr": "^2.0.4", "toposort": "^2.0.2" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.17.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz", + "integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@types/lodash": { + "version": "4.14.178", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz", + "integrity": "sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==" + } } } } diff --git a/package.json b/package.json index bf3ae27..9451d3e 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,7 @@ "lodash.get": "^4.4.2", "lodash.set": "^4.3.2", "react-transition-group": "^4.4.1", - "yup": "^0.30.0" + "yup": "^0.32.11" }, "publishConfig": { "access": "public" diff --git a/src/ButtonGroup/index.tsx b/src/ButtonGroup/index.tsx index 01aab0d..c56e5d4 100644 --- a/src/ButtonGroup/index.tsx +++ b/src/ButtonGroup/index.tsx @@ -3,9 +3,10 @@ import cx from 'classnames' import Button from '../Button' import Dropdown from '../Dropdown' import { ButtonGroupProps, ButtonGroupActionProps } from './props' +import { noop } from '../utils' export default function ButtonGroup(props: ButtonGroupProps) { - const { + let { align = 'center', justify = 'left', gap = '', @@ -17,7 +18,7 @@ export default function ButtonGroup(props: ButtonGroupProps) { containerClass = '' } = props - const computedGroupClass = {} + let computedGroupClass = {} if (gap) { computedGroupClass[`ui-kit-btn-group__has-gap gap__${gap}`] = true } @@ -26,35 +27,42 @@ export default function ButtonGroup(props: ButtonGroupProps) { computedGroupClass[`ui-kit-btn-group__space-${verticalSpacing}`] = true } - const renderItem = (action: ButtonGroupActionProps) => { - if (action.type === 'dropdown') { + let renderItem = (action: ButtonGroupActionProps) => { + let { + type, + onClick = noop, + label, + extraProps = {}, + component + } = action; + if (type === 'dropdown') { return ( action.onClick(item)} + textContent={label} + onClick={(item: any) => onClick(item)} options={action.options || []} position={action.dropdownPosition || 'right'} theme={theme} variant={variant} triggerSize={size} - {...action.extraProps} + {...extraProps} /> ) } if (action.type === 'custom') { - return action.component + return component } return ( ) } diff --git a/src/ButtonGroup/props.ts b/src/ButtonGroup/props.ts index cfa4f66..3df1168 100644 --- a/src/ButtonGroup/props.ts +++ b/src/ButtonGroup/props.ts @@ -9,7 +9,7 @@ export declare type ButtonGroupActionType = 'button' | 'dropdown' | 'custom' export interface ButtonGroupActionProps { label: any - onClick: Function + onClick?: Function type?: ButtonGroupActionType extraProps?: { [k: string]: any diff --git a/src/DatePicker/DatePickerElement.tsx b/src/DatePicker/DatePickerElement.tsx index e09f93a..097fd39 100644 --- a/src/DatePicker/DatePickerElement.tsx +++ b/src/DatePicker/DatePickerElement.tsx @@ -19,7 +19,8 @@ function DatePickerElement(props: DatePickerElementProps, ref: any) { endDate, monthMenuRef, yearMenuRef, - onChange + onChange, + id } = props let [menuState, setMenuState] = useState({ month: false, year: false }) @@ -42,6 +43,7 @@ function DatePickerElement(props: DatePickerElementProps, ref: any) { >
diff --git a/src/DatePicker/index.tsx b/src/DatePicker/index.tsx index 0f5e774..e0e992d 100644 --- a/src/DatePicker/index.tsx +++ b/src/DatePicker/index.tsx @@ -48,8 +48,8 @@ export default class Datepicker extends Component { window.addEventListener('click', this.addBackDrop, false) window.addEventListener('resize', this.handleResize, false) @@ -313,10 +317,8 @@ export default class Datepicker extends Component { if (!timestamp) return ''; - let dateObject = new Date(timestamp) - let month = dateObject.getMonth() + 1 - let date = dateObject.getDate() - return dateObject.getFullYear() + '-' + (month < 10 ? '0' + month : month) + '-' + (date < 10 ? '0' + date : date) + let date = new Date(timestamp) + return dayjs(date).format(this.dateFormat); } setDateToInput = (timestamp: any) => { @@ -450,6 +452,7 @@ export default class Datepicker extends Component ( { diff --git a/src/DatePicker/props.ts b/src/DatePicker/props.ts index 0b171cf..0a0943d 100644 --- a/src/DatePicker/props.ts +++ b/src/DatePicker/props.ts @@ -10,7 +10,7 @@ export interface DatePickerProps extends InputProps { max?: Date closeOnSelect?: boolean format?: string - className?: string + inputContainerClass?: string transitionDuration?: number value?: Date defaultValue?: Date @@ -34,6 +34,7 @@ export interface DatePickerState { } export interface DatePickerElementProps { + id?: string transitionState: TransitionState transitionDuration?: number position?: any @@ -48,6 +49,7 @@ export interface DatePickerElementProps { } export interface DatePickerHeaderProps { + id?: string selectedMonth: number selectedYear: number startDate: Date diff --git a/src/Dialog/Dialog.tsx b/src/Dialog/Dialog.tsx index 960f284..4dcf037 100644 --- a/src/Dialog/Dialog.tsx +++ b/src/Dialog/Dialog.tsx @@ -113,7 +113,7 @@ export default function Dialog(props: DialogProps) { tabIndex={0} onClick={() => onClose()} data-testid={`close-${id}`} - className='ui-kit-dialog-close cursor-pointer element-flex-center has-hover-effect radius-circle' + className='ui-kit-dialog-close cursor-pointer element-flex-center has-hover-effect border-radius' > diff --git a/src/Form/Form.tsx b/src/Form/Form.tsx index 08d3f1e..d360fc5 100644 --- a/src/Form/Form.tsx +++ b/src/Form/Form.tsx @@ -11,28 +11,47 @@ import Checkbox from '../Checkbox' import Radio from '../Radio' import Alert from '../Alert' import FormData from './form-data' +import { noopWithReturn, parseTranslationOptions } from './utils' import { isDefined, isEqual, isFunction, noop } from '../utils' import { FormFields, FormProps, FormState, AttributesType } from './props' const SERVICE_METHOD_NOT_AVAILABLE = 'Service method is not configured' export default class Form extends Component { - // fieldsHash: any constructor(props: FormProps) { super(props) - let { fields, data, abortEarly = true, t, idField, stripUnchanged } = props + let { + fields, + data, + abortEarly = true, + t, + idField, + stripUnchanged, + ignoreDefaultTranslationOption + } = props this.state = { - formData: new FormData(data, fields, { abortEarly, t, idField, stripUnchanged }), + formData: new FormData(data, fields, { + abortEarly, + t, + idField, + stripUnchanged, + ignoreDefaultTranslationOption + }), errors: {}, genericError: null, submitting: false, dirty: false, startValidate: false } - // this.fieldsHash = this.props.fields.reduce((hash, field) => { - // hash[field.name] = { ...field } - // return hash - // }, {}) + } + + get t() { + let { t = noopWithReturn } = this.props + return t + } + + get hasTranslation() { + return !isEmpty(this.t) } get formData() { @@ -87,8 +106,8 @@ export default class Form extends Component { } } - serialize = (options: any = {}) => { - return this.formData.serialize(options) + serialize = () => { + return this.formData.serialize({ stripUnknown: true }) } handleSubmit = (e: React.FormEvent) => { @@ -167,6 +186,7 @@ export default class Form extends Component { } renderField = (field: FormFields) => { + let { name: formName } = this.props let { errors } = this.state let { type = 'text', @@ -177,12 +197,28 @@ export default class Form extends Component { disabledIf = () => false, hiddenIf = () => false, label, + placeholder = componentProps.placeholder, + message = componentProps.message, name, maxLength, customComponent = null, + transform, + errorMessage, + translate, + translateOptions = {}, ...restProps } = field + if (this.hasTranslation) { + if (!this.props.ignoreDefaultTranslationOption) { + translateOptions = { entity: label, ...translateOptions } + } + translateOptions = parseTranslationOptions(translateOptions, this.t) + if (label) label = this.t(label, translateOptions) + if (placeholder) placeholder = this.t(placeholder, translateOptions) + if (message) message = this.t(message, translateOptions); + } + let customOptions = { _id: this._id, isNew: this.isNew } if (hiddenIf(this.data, customOptions)) return null @@ -197,11 +233,16 @@ export default class Form extends Component { disabled, required, label, + placeholder, type, - name, + name: `${formName}[${name}]`, id: name, value: get(this.data, name), - onChange: (e: any) => this.handleInputChange(name, e.target.value), + onChange: (e: any) => { + let { value } = e.target + if (value && type === 'number') value = parseFloat(value) + return this.handleInputChange(name, value) + }, error } @@ -269,6 +310,33 @@ export default class Form extends Component { return } + renderFormFields = (fields: Array) => { + return fields.map((field, index) => { + if (field.group && Array.isArray(field.fields)) { + let { + fields: groupFields, + groupKey = index, + groupTitle, + groupType = 'column', + groupClass = '' + } = field; + return ( +
+ {groupTitle &&

{groupTitle}

} + {this.renderFormFields(groupFields)} +
+ ) + } + return ( + + { + this.renderField(field) + } + + ) + }) + } + render() { let { fields, @@ -293,15 +361,7 @@ export default class Form extends Component { }
- { - renderableFields.map(field => ( - - { - this.renderField(field) - } - - )) - } + {this.renderFormFields(renderableFields)}
{extra}
diff --git a/src/Form/form-data.ts b/src/Form/form-data.ts index e70c96f..e1f5d9e 100644 --- a/src/Form/form-data.ts +++ b/src/Form/form-data.ts @@ -6,16 +6,14 @@ import { FormFields, ChangedAttributesType, AttributesType } from './props' import Schema from './schema' import { isMultiValueField, isDateField, isBooleanField } from './utils' -const noopWithReturn = (str: string) => str -const getNumberValue = (value: any) => value ? (isNaN(value) ? '' : value) : '' -const getDateValue = (value: any) => value instanceof Date ? value : null +const getNumberValue = (value: any) => value ? (isNaN(value) ? undefined : value) : undefined +const getDateValue = (value: any) => value instanceof Date ? value : undefined export default class FormData extends Schema { fields: Array data: object abortEarly: boolean strict: boolean = false - t: Function idField: string isNew: boolean _id: string @@ -25,16 +23,14 @@ export default class FormData extends Schema { _initialValue: object = {} constructor(data: object, fields: Array, options: any) { - super(fields) + super(fields, options) this.fields = fields let { abortEarly = true, - t = noopWithReturn, idField = 'id', stripUnchanged = false } = options this.abortEarly = abortEarly - this.t = t this.idField = idField this.isNew = true this.stripUnchanged = stripUnchanged @@ -65,29 +61,49 @@ export default class FormData extends Schema { return data } - handleValidationError(validationError: ValidationError): any { - let errors: any = {} - let { path, message, inner: innerError} = validationError + handleValidationError(validationError: ValidationError, validation: any): any { + validation.isValid = false + let { + inner: innerError, + path, + message + } = validationError if (this.abortEarly) { - errors[path] = this.t(message) + if (path) validation.errors[path] = message + else validation.genericError = [message] } else { for (let error of innerError) { - if (!errors[error.path]) errors[error.path] = error.message + if (error.path) { + if (!validation.errors[error.path]) validation.errors[error.path] = message + } else { + validation.genericError = (validation.genericError || []).concat([message]) + } } } - return errors + return validation } validate = () => { let validation: any = { isValid: true, - errors: {} + errors: {}, + genericError: null } - try { - this.schema.validateSync(this.data, this.validationOption) - } catch (validationError: any) { - validation.isValid = false - validation.errors = this.handleValidationError(validationError) + if (this.abortEarly) { + for (let field of this.validationOrder) { + try { + this.schema.validateSyncAt(field, this.data, { strict: true }) + } catch (validationError: any) { + validation = this.handleValidationError(validationError, validation) + break + } + } + } else { + try { + this.schema.validateSync(this.data, this.validationOption) + } catch (validationError: any) { + validation = this.handleValidationError(validationError, validation) + } } return validation } @@ -95,7 +111,10 @@ export default class FormData extends Schema { serialize = (options: any = {}) => { let parsedData: any = {} try { - parsedData = this.schema.cast(this.processableData, options) + parsedData = this.schema.cast(this.processableData, { + assert: false, + ...options + }) } catch (error) { console.error(error) } @@ -127,17 +146,21 @@ export default class FormData extends Schema { return this } - _setupData = (fields: Array, data: object) => { + _setupData = (fields: Array, data: object, formData: any = {}) => { let today = new Date() - return fields.filter((f) => !f.hidden).reduce((formData, field) => { + let filteredFields = fields.filter((f) => !f.hidden) + + for (let field of filteredFields) { + if (field.group && Array.isArray(field.fields)) { + this._setupData(field.fields, data, formData) + continue + } let { name, getter, component = 'TextInput', componentProps = {}, - type = 'text', default: defaultValue = '' + type = 'text', default: defaultValue = undefined } = field - let previous, current; let multiple = Boolean(componentProps.multiple) let value = getter && isFunction(getter) ? getter(data) : get(data, name) - previous = current = value; if (isMultiValueField(component, multiple)) { if (!value) value = [] else if (!Array.isArray(value)) value = [value] @@ -152,20 +175,20 @@ export default class FormData extends Schema { } if (isBooleanField(component, type)) { value = Boolean(value) - defaultValue = typeof defaultValue === 'boolean' ? defaultValue : field.nullable ? null : false + defaultValue = typeof defaultValue === 'boolean' ? defaultValue : field.nullable ? undefined : false } if (type === 'number') { value = getNumberValue(value) defaultValue = getNumberValue(defaultValue) } else { - value = value || defaultValue + value = value || defaultValue || undefined } - if (![[], '', null, undefined, today].includes(value)) current = value set(formData, name, value) set(this._initialValue, name, value) - set(this.attributes, name, [previous, current]) - return formData - }, {}) + set(this.attributes, name, [value]) + } + + return formData } hasDirtyAttributes = () => { diff --git a/src/Form/props.ts b/src/Form/props.ts index 0b3dab4..cd2dfb8 100644 --- a/src/Form/props.ts +++ b/src/Form/props.ts @@ -1,25 +1,77 @@ +import { + TransformFunction +} from 'yup/es/types' import FormData from './form-data' export type FieldComponents = 'TextInput' | 'TextArea' | 'Checkbox' | 'Checkbox.Group' | 'Select' | 'Radio' | 'Radio.Group' | 'DatePicker' | 'MonthPicker' | 'Custom' -export type FieldTypes = 'text' | 'string' | 'email' | 'url' | 'password' | 'number' | 'date' | 'datetime' | 'boolean' | 'bool' | 'array' | 'object' +export type FieldTypes = 'text' | 'tel' | 'string' | 'email' | 'url' | 'password' | 'number' | 'date' | 'datetime' | 'boolean' | 'bool' | 'array' | 'object' +interface ErrorMessageAttrs { + required?: string + email?: string + url?: string + default?: string + exactLength?: string +} + +export interface FieldValidationAttrs { + enum?: Array + min?: number | Date + max?: number | Date + length?: number + when?: Array + pattern?: RegExp + transform?: TransformFunction + errorMessage?: ErrorMessageAttrs +} -export interface FormFields { +export interface FormFields extends FieldValidationAttrs { + // Field properties name: string label?: string + placeholder?: string + + // Field value getter getter?: Function - required?: boolean - disabled?: boolean - hidden?: boolean + + // Field types type?: FieldTypes - component?: FieldComponents customComponent?: any + component?: FieldComponents componentProps?: any - disabledIf?: Function - hiddenIf?: Function + + // Field change control onInputChange?: Function - editable?: boolean + + // Default value of the field default?: any + + // Field behaviours + required?: boolean + disabled?: boolean + hidden?: boolean nullable?: boolean + editable?: boolean + optional?: boolean + + // Field controlled behaviours + disabledIf?: Function + hiddenIf?: Function + + // Field attribute validations + maxLength?: number + + // Custom support for translation + translateOptions?: any + + // manage group fields + group?: boolean + groupKey?: string // unique key for group + groupTitle?: string + groupType?: 'row' | 'column' + groupClass?: string + fields?: Array + + // Any custom field attributes - maintain lowercase [k: string]: any } @@ -66,6 +118,7 @@ export interface FormProps { customValidation?: Function constructParams?: Function t?: Function + ignoreDefaultTranslationOption?: boolean submitOnlyIfValid?: boolean abortEarly?: boolean stripUnchanged?: boolean diff --git a/src/Form/schema.ts b/src/Form/schema.ts index 4687d94..0515881 100644 --- a/src/Form/schema.ts +++ b/src/Form/schema.ts @@ -1,7 +1,8 @@ import * as yup from 'yup' import dayjs from 'dayjs' -import { FieldComponents, FieldTypes } from './props' -import { emailRegex } from './utils' +import isEmpty from 'is-empty' +import { FormFields } from './props' +import { noopWithReturn, parseTranslationOptions } from './utils' const { object, @@ -9,170 +10,190 @@ const { number, bool, array, - date, - mixed + date } = yup -interface ErrorMessageAttrs { - required?: string - email?: string - default?: string -} +export default class Schema { + _schema: yup.AnyObjectSchema + _fieldOrder: Array -interface FieldAttrs { - name?: string - type?: FieldTypes - component?: FieldComponents - required?: boolean - email?: boolean - url?: boolean - nullable?: boolean - default?: any - enum?: Array - min?: number | Date - max?: number | Date - hidden?: boolean - when?: Array - transform?: yup.TransformFunction - errorMessage?: ErrorMessageAttrs -} + _options: any -interface SchemaAttrs { - [key: string]: FieldAttrs -} - -export default class Schema { - _schema: yup.ObjectSchema - constructor(attrs: SchemaAttrs | Array) { - let fieldSchema = this._setupSchema(attrs) - this._schema = object().shape(fieldSchema) + constructor(attrs: Array, options: any = {}) { + this._options = options + let shape = this._setupSchema(attrs) + this._fieldOrder = Object.keys(shape) + let organizedShape = Object.entries(shape).reverse().reduce((prev, [key, value]) => ({ ...prev, [key]: value }), {}) + this._schema = object().shape(organizedShape) } get schema() { return this._schema } - _setupSchema = (attrs: SchemaAttrs | Array) => { - let _schema: any = {} - for (let [attrName, props] of Object.entries(attrs).reverse()) { - let { - type, - default: defaultValue = '', - hidden, - transform = null, - nullable = false, - required, - component, - name = '', - errorMessage = {}, - ...rest - } = props - attrName = name || attrName - if (hidden) { + get validationOrder() { + return this._fieldOrder + } + + get t() { + let { t = noopWithReturn } = this._options + return t + } + + get hasTranslation() { + return !isEmpty(this._options.t) + } + + _setupSchema = (fields: Array, _schema: any = {}) => { + for (let field of fields) { + if (field.hidden || field.disabled) { continue } - - let { - required: requiredMessage = 'Please fill this required field' - } = errorMessage - - let field - switch (type) { - case 'date': - case 'datetime': - field = date().nullable(true) - if (Array.isArray(rest.when)) { - let [dependentFields, handler] = rest.when - field = field.when(dependentFields, handler) - } else if (required) field = field.required(requiredMessage) - - if (defaultValue instanceof Date) field = field.default(defaultValue) - if (rest.min instanceof Date) field = field.min(dayjs(rest.min).startOf('d').toDate()) - if (rest.max instanceof Date) field = field.max(dayjs(rest.max).endOf('d').toDate()) - break - case 'number': - let { - nullable: _nullable = true - } = props - field = number().nullable(_nullable) - if (Array.isArray(rest.when)) { - let [dependentFields, handler] = rest.when - field = field.when(dependentFields, handler) - } else if (required) field = field.required(requiredMessage) - - if (typeof defaultValue === 'number') field = field.default(defaultValue) - if (rest.enum?.length) field = field.oneOf(rest.enum) - if (typeof rest.min === 'number') field = field.min(rest.min) - if (typeof rest.max === 'number') field = field.max(rest.max) - - if (!transform) transform = (value: any) => isNaN(value) ? null : value - break - case 'bool': - case 'boolean': - field = bool().nullable(nullable) - if (required) field = field.required(requiredMessage) - break - case 'string': - case 'text': - case 'email': - case 'url': - case 'password': - field = string().nullable(nullable) - if (Array.isArray(rest.when)) { - let [dependentFields, handler] = rest.when - field = field.when(dependentFields, handler) - } else { - if (required) field = field.required(requiredMessage) - else if (nullable) field = field.nullable() - } - field = field.default(defaultValue) - field = field.trim() - let { - email = (type === 'email'), - url = (type === 'url') - } = rest - if (email) field = field.matches(emailRegex, { - message: 'Enter valid email address' - }) - if (url) field = field.url() - if (rest.enum?.length) field = field.oneOf(rest.enum) - break - case 'array': - field = array() - if (Array.isArray(rest.when)) { - let [dependentFields, handler] = rest.when - field = field.when(dependentFields, handler) - } else { - if (required) field = field.required(requiredMessage) - else if (nullable) field = field.nullable() - } - if (!Array.isArray(defaultValue)) defaultValue = nullable ? null : [] - field = field.default(defaultValue) - break - case 'object': - field = object().nullable(nullable) - if (Array.isArray(rest.when)) { - let [dependentFields, handler] = rest.when - field = field.when(dependentFields, handler) - } else if (required) field = field.required(requiredMessage) - field = field.default(defaultValue || undefined) - default: - field = mixed().nullable(nullable) - if (Array.isArray(rest.when)) { - let [dependentFields, handler] = rest.when - field = field.when(dependentFields, handler) - } else if (required) field = field.required(requiredMessage) - field = field.default(defaultValue || null) - break + if (field.group && Array.isArray(field.fields)) { + this._setupSchema(field.fields, _schema) + continue } + let schema = this._getFieldSchema(field) + _schema[field.name] = schema + } + + return _schema + } - if (typeof transform === 'function') { - field = field.transform(transform) + _getFieldSchema = (field: FormFields) => { + let { + label = '', + type, + default: defaultValue, + hidden, + transform = null, + required, + optional = !required, + component, + name, + errorMessage = {}, + ...rest + } = field + + defaultValue = defaultValue || undefined + + if (this.hasTranslation) { + let { translateOptions = {} } = field + if (!this._options.ignoreDefaultTranslationOption) { + translateOptions = { + entity: label, + length: rest.length, + ...translateOptions + } + } + translateOptions = parseTranslationOptions(translateOptions, this.t) + for (let key in errorMessage) { + errorMessage[key] = this.t(errorMessage[key], translateOptions) } + } + + let { + required: requiredMessage = 'Please fill this required field', + email: emailErrorMessage = 'Enter valid email address', + url: urlErrorMessage = 'Enter valid url', + exactLength = `Should be exactly ${rest.length} characters` + } = errorMessage + + let schema + switch(type) { + case 'date': + case 'datetime': + schema = date().default(defaultValue) + if (Array.isArray(rest.when)) { + let [dependentFields, handler] = rest.when + schema = schema.when(dependentFields, handler) + } else if (required) { + schema = schema.required(requiredMessage) + } else { + schema = schema.optional() + } + if (rest.min instanceof Date) schema = schema.min(dayjs(rest.min).startOf('d').toDate()) + if (rest.max instanceof Date) schema = schema.max(dayjs(rest.max).endOf('d').toDate()) + break + case 'number': + schema = number().default(defaultValue) + if (Array.isArray(rest.when)) { + let [dependentFields, handler] = rest.when + schema = schema.when(dependentFields, handler) + } else if (required) { + schema = schema.required(requiredMessage) + } else { + schema = schema.optional() + } - _schema[attrName] = field + if (rest.enum?.length) schema = schema.oneOf(rest.enum) + if (typeof rest.min === 'number') schema = schema.min(rest.min) + if (typeof rest.max === 'number') schema = schema.max(rest.max) + if (!transform) transform = (value: any) => isNaN(value) ? undefined : value + break + case 'bool': + case 'boolean': + schema = bool() + if (required) schema = schema.required(requiredMessage) + else schema = schema.optional() + break + case 'array': + if (!Array.isArray(defaultValue)) defaultValue = optional ? undefined : [] + schema = array().default(defaultValue) + if (Array.isArray(rest.when)) { + let [dependentFields, handler] = rest.when + schema = schema.when(dependentFields, handler) + } else { + if (required) schema = schema.required(requiredMessage) + else schema = schema.optional() + } + if (typeof rest.min === 'number') schema = schema.max(rest.min) + if (typeof rest.max === 'number') schema = schema.max(rest.max) + break + case 'object': + schema = object().default(defaultValue) + if (Array.isArray(rest.when)) { + let [dependentFields, handler] = rest.when + schema = schema.when(dependentFields, handler) + } else if (required) { + schema = schema.required(requiredMessage) + } else { + schema = schema.optional() + } + break + case 'string': + case 'password': + case 'url': + case 'email': + case 'text': + case 'tel': + default: + schema = string().trim().default(defaultValue) + if (Array.isArray(rest.when)) { + let [dependentFields, handler] = rest.when + schema = schema.when(dependentFields, handler) + } else if (required) { + schema = schema.required(requiredMessage) + } else { + schema = schema.optional() + } + if (type === 'email') { + if (rest.pattern) schema = schema.matches(rest.pattern, { message: emailErrorMessage }) + else schema = schema.email(emailErrorMessage) + } + if (type === 'url') { + if (rest.pattern) schema = schema.matches(rest.pattern, { message: urlErrorMessage }) + else schema = schema.url(urlErrorMessage) + } + if (rest.enum?.length) schema = schema.oneOf(rest.enum) + if (rest.length) schema = schema.length(rest.length, exactLength) + break } - return _schema + if (typeof transform === 'function') { + schema = schema.transform(transform) + } + + return schema } } diff --git a/src/Form/utils.ts b/src/Form/utils.ts index 6ee0df4..aea9154 100644 --- a/src/Form/utils.ts +++ b/src/Form/utils.ts @@ -1,3 +1,5 @@ +import { isString, isObject } from '../utils' + export const MULTI_VALUE_FIELDS = ['Select', 'Checkbox.Group'] export const BOOLEAN_FIELDS = ['Checkbox'] export const emailRegex = /^(([^<>()[\]\\.,:\s@"]+(\.[^<>()[\]\\.,:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ @@ -14,3 +16,17 @@ export const isSelectField = (component: string = '', multiple: boolean = false) export const isMultiValueField = (component: string = '', multiple: boolean = false) => { return MULTI_VALUE_FIELDS.includes(component) && !isSelectField(component, multiple); } + +export const noopWithReturn = (str: string) => str + +export const parseTranslationOptions = (options: any = {}, t: Function = noopWithReturn) => { + for (let key in options) { + let option = options[key] + if (isString(option)) { + options[key] = t(option) + } else if (isObject(option) && !option.skipTranslation) { + options[key] = t(option.key) + } + } + return options +} diff --git a/src/MonthPicker/index.tsx b/src/MonthPicker/index.tsx index f88056c..36b1141 100644 --- a/src/MonthPicker/index.tsx +++ b/src/MonthPicker/index.tsx @@ -39,6 +39,7 @@ function MonthPicker(props: MonthPickerProps, ref: any) { error, inputSize = 'default', animate = true, + inputClass = '', ...inputProps } = props @@ -114,6 +115,7 @@ function MonthPicker(props: MonthPickerProps, ref: any) { width={yearWidth} selected={[year]} containerClass='mb-0 w-auto mr-8' + inputClass={inputClass} setMenuRef={yearMenuRef} disabled={disabled || yearsList.length < 2} onOpen={() => onOpen('year')} @@ -130,6 +132,7 @@ function MonthPicker(props: MonthPickerProps, ref: any) { width={width} selected={[month]} containerClass='mb-0 w-auto' + inputClass={inputClass} setMenuRef={monthMenuRef} disabled={disabled || monthList.length < 2} onOpen={() => onOpen('month')} diff --git a/src/MonthPicker/props.ts b/src/MonthPicker/props.ts index 68447de..6b6692e 100644 --- a/src/MonthPicker/props.ts +++ b/src/MonthPicker/props.ts @@ -18,4 +18,5 @@ export interface MonthPickerProps extends InputProps { className?: string borderless?: boolean animate?: boolean + inputClass?: string } diff --git a/src/Radio/RadioGroup.tsx b/src/Radio/RadioGroup.tsx index 2a7544c..16ea190 100644 --- a/src/Radio/RadioGroup.tsx +++ b/src/Radio/RadioGroup.tsx @@ -12,11 +12,13 @@ export function RadioGroup(props: RadioGroupProps) { required = false, variant = 'row', error, - id + id, + disabled = false, + containerClass = '' } = props return ( -
+
{ label && } @@ -29,7 +31,7 @@ export function RadioGroup(props: RadioGroupProps) { label={option.label} checked={option.value === value} value={option.value} - disabled={option.disabled} + disabled={disabled || option.disabled} onChange={(e: Event) => onChange(e)} /> )) diff --git a/src/Radio/props.ts b/src/Radio/props.ts index 4bddfbf..54f576f 100644 --- a/src/Radio/props.ts +++ b/src/Radio/props.ts @@ -27,4 +27,6 @@ export interface RadioGroupProps { options: Array variant: 'row' | 'column', error?: any + disabled?: boolean + containerClass?: string } diff --git a/src/Select/BasicSelect.tsx b/src/Select/BasicSelect.tsx index c9ca1fb..5df71bf 100644 --- a/src/Select/BasicSelect.tsx +++ b/src/Select/BasicSelect.tsx @@ -35,19 +35,19 @@ class BasicSelect extends PureComponent { } UNSAFE_componentWillReceiveProps(nextProps: SelectProps) { - const { options = [], selected: nextSelected = [], defaultFirstItemSelected } = nextProps - const selected = optionsMap([...nextSelected], nextProps) - const nextOptions = optionsMap([...options], nextProps) - const activeIndex = this._getActiveIndex(selected, defaultFirstItemSelected) - const nextState: SelectState = { + let { options = [], selected: nextSelected = [], defaultFirstItemSelected } = nextProps + let selected = optionsMap([...nextSelected], nextProps) + let nextOptions = optionsMap([...options], nextProps) + let activeIndex = this._getActiveIndex(selected, defaultFirstItemSelected) + let nextState: SelectState = { value: this.state.value, activeIndex, id: this.state.id } - const { options: currentOptions, value } = this.state + let { options: currentOptions, value } = this.state if (!isEqual(nextOptions, currentOptions)) { nextState.options = nextOptions - const results = filterResultsFromOptions([...nextOptions], { + let results = filterResultsFromOptions([...nextOptions], { ...nextProps, id: this.state.id, value, @@ -64,17 +64,17 @@ class BasicSelect extends PureComponent { } componentDidUpdate(_prevProps: SelectProps, prevState: SelectState) { - const { value, options = [] } = this.state + let { value, options = [] } = this.state if (prevState.value !== value) { - const { defaultFirstItemSelected } = this.props - const results = filterResultsFromOptions([...options], { + let { defaultFirstItemSelected } = this.props + let results = filterResultsFromOptions([...options], { ...this.props, id: this.state.id, value, activeIndex: -1 }) - const activeIndex = defaultFirstItemSelected ? 0 : -1 - const activeItem = defaultFirstItemSelected ? results[0] : null + let activeIndex = defaultFirstItemSelected ? 0 : -1 + let activeItem = defaultFirstItemSelected ? results[0] : null this.setState({ results, activeItem, @@ -93,7 +93,7 @@ class BasicSelect extends PureComponent { _getActiveIndex = (selected: Array, firstItemSelected?: boolean) => { if (selected.length) { - const { results = [] } = this.state + let { results = [] } = this.state return getActiveIndex([...results], selected, this.props) } @@ -101,7 +101,7 @@ class BasicSelect extends PureComponent { } _getActiveItem = (state = this.state) => { - const { + let { activeIndex, results = [] } = state @@ -109,12 +109,12 @@ class BasicSelect extends PureComponent { } _handlePropsChange = (nextProps: SelectProps, nextState: SelectState, nextOptions: Array, selected: Array) => { - const { + let { multiple, disabled, labelKey } = nextProps - const { + let { selected: currentSelected } = this.state if (multiple !== this.props.multiple) { @@ -170,8 +170,8 @@ class BasicSelect extends PureComponent { } _checkMenuPosition = () => { - const nextMenuPosition = this._getMenuPosition() - const { menuPosition } = this.state + let nextMenuPosition = this._getMenuPosition() + let { menuPosition } = this.state if (!isEqual(nextMenuPosition, menuPosition)) { this._setMenuPosition(nextMenuPosition) } @@ -187,16 +187,16 @@ class BasicSelect extends PureComponent { dropup: false } } - const { maxDropdownHeight = MAXIMUM_DROPDOWN_HEIGHT, dropup, searchable = false } = this.props - const { results = [] } = this.state - const menuHeight = getMenuHeight(maxDropdownHeight, results.length, searchable) - const body = document.documentElement || document.body - const position = getOffset(this.input) + let { maxDropdownHeight = MAXIMUM_DROPDOWN_HEIGHT, dropup, searchable = false } = this.props + let { results = [] } = this.state + let menuHeight = getMenuHeight(maxDropdownHeight, results.length, searchable) + let body = document.documentElement || document.body + let position = getOffset(this.input) let bottom: string | number = 'auto' let top = position.y + position.height + 2 let _dropup = false - const offset = Math.max(body.scrollTop, (body.scrollHeight - window.innerHeight)) - const offsetHeight = body.scrollHeight - offset + let offset = Math.max(body.scrollTop, (body.scrollHeight - window.innerHeight)) + let offsetHeight = body.scrollHeight - offset if (dropup || ((position.bottom + menuHeight) > offsetHeight)) { top = 'auto' bottom = offsetHeight - position.y + 2 @@ -220,8 +220,8 @@ class BasicSelect extends PureComponent { _handleInputChange = (e: React.ChangeEvent) => { e.persist() - const value = e.currentTarget.value - const clearSelection = !this.props.multiple && this.state.selected?.length + let value = e.currentTarget.value + let clearSelection = !this.props.multiple && this.state.selected?.length this.setState((prevState) => { return { isDirty: true, @@ -229,15 +229,15 @@ class BasicSelect extends PureComponent { value, } }, () => { - const { onInputChange = noop, onChange = noop } = this.props + let { onInputChange = noop, onChange = noop } = this.props onInputChange(value, e) clearSelection && onChange([]) }) } _handleActiveIndexChange = (activeIndex: number) => { - const { results = [] } = this.state - const activeItem = activeIndex === -1 ? null : results[activeIndex] + let { results = [] } = this.state + let activeItem = activeIndex === -1 ? null : results[activeIndex] this.setState({ activeIndex, activeItem @@ -245,8 +245,8 @@ class BasicSelect extends PureComponent { } _handleKeyDown = (e: React.KeyboardEvent) => { - const { activeItem } = this.state - const { searchable } = this.props + let { activeItem } = this.state + let { searchable } = this.props // Skip most actions when the menu is hidden. if (!this._isMenuOpen) { @@ -306,14 +306,14 @@ class BasicSelect extends PureComponent { !this._isMenuOpen ? this._openMenu() : this._closeMenu() } this.__justClicked = true - const { onInputFocus = noop } = this.props + let { onInputFocus = noop } = this.props onInputFocus(e) } _onBlur = (e: React.FocusEvent) => { e.persist() this._hasFocus && this._setFocus(false) - const { onInputBlur = noop } = this.props + let { onInputBlur = noop } = this.props onInputBlur(e) } @@ -322,7 +322,7 @@ class BasicSelect extends PureComponent { [ACTUAL_VALUE]: selected } = option if (this.props.multiple) { - const prevSelected = [...(this.state.selected || [])].map(s => s[ACTUAL_VALUE]) + let prevSelected = [...(this.state.selected || [])].map(s => s[ACTUAL_VALUE]) selected = [...prevSelected, selected] } else { selected = [selected] @@ -333,8 +333,8 @@ class BasicSelect extends PureComponent { _onRemoveSelected = (option: OptionProps) => { if (!this.props.multiple) return - const { labelKey, onChange = noop } = this.props - const selected = [...(this.state.selected || [])].filter(s => s[labelKey] !== option[labelKey]).map(ms => ms[ACTUAL_VALUE]) + let { labelKey, onChange = noop } = this.props + let selected = [...(this.state.selected || [])].filter(s => s[labelKey] !== option[labelKey]).map(ms => ms[ACTUAL_VALUE]) onChange(selected) this._isMenuOpen && this._closeMenu() } @@ -369,13 +369,13 @@ class BasicSelect extends PureComponent { this._checkMenuPosition() this._enableAutoScroll() this.setState((prevState: SelectState) => { - const nextState: SelectState = { + let nextState: SelectState = { id: prevState.id, value: prevState.value, open: true, activeIndex: -1 } - const { results = [], selected = [] } = prevState + let { results = [], selected = [] } = prevState if (!prevState.searchable) { nextState.activeIndex = getActiveIndex(results, selected.length ? selected : [], this.props) } else { @@ -400,7 +400,7 @@ class BasicSelect extends PureComponent { focus: false, isDirty: false, }, () => { - const { + let { // multiple, onClose = noop } = this.props @@ -476,20 +476,20 @@ class BasicSelect extends PureComponent { } _menuContainer = () => { - const { + let { animate = true, transitionDuration = animate ? ANIMATION_TIMER : 0, searchable } = this.props - const { + let { menuPosition, open, value, id } = this.state - const mergedStateAndProps = { + let mergedStateAndProps = { ...this.props, ...this.state, } @@ -534,7 +534,7 @@ class BasicSelect extends PureComponent { } _getRightIcon = () => { - const { + let { showRightIcon = true, icons } = this.props @@ -554,7 +554,7 @@ class BasicSelect extends PureComponent { }) get inputHeight(): any { - const { + let { inputSize = 'default', height = HEIGHT_MAP[inputSize] || SELECT_HEIGHT } = this.props @@ -562,7 +562,7 @@ class BasicSelect extends PureComponent { } _getContainerStyle = () => { - const { + let { multiple, borderless, textOnly @@ -575,25 +575,25 @@ class BasicSelect extends PureComponent { } _getExtraProps = () => { - const { + let { searchable, // multiple } = this.props - const extraProps: ExtraInputProps = searchable ? {} : { onKeyDown: this._handleKeyDown } + let extraProps: ExtraInputProps = searchable ? {} : { onKeyDown: this._handleKeyDown } // if (multiple) extraProps.ref = this._setBackgroundInputRef return extraProps } _getPortalTarget = () => { - const { + let { container } = this.props return canUseDOM && container ? document.querySelector(container) : null } render() { - const { + let { disabled = false, inputProps, placeholder, @@ -613,19 +613,19 @@ class BasicSelect extends PureComponent { width = SELECT_WIDTH } = this.props - const { + let { open, selected, id } = this.state - const rightIcon = this._getRightIcon() - const leftIcon = this._getLeftIcon() - const containerStyle = this._getContainerStyle() - const extraProps = this._getExtraProps() - const portalTarget = this._getPortalTarget() - const menuContainer = this._menuContainer() - const isTextOnlyAndBorderLess = textOnly && borderless + let rightIcon = this._getRightIcon() + let leftIcon = this._getLeftIcon() + let containerStyle = this._getContainerStyle() + let extraProps = this._getExtraProps() + let portalTarget = this._getPortalTarget() + let menuContainer = this._menuContainer() + let isTextOnlyAndBorderLess = textOnly && borderless return ( <> diff --git a/src/Select/InputWrapper.tsx b/src/Select/InputWrapper.tsx index 606e495..e701cd8 100644 --- a/src/Select/InputWrapper.tsx +++ b/src/Select/InputWrapper.tsx @@ -8,13 +8,13 @@ import { noop } from '../utils' import Loader from '../Loader' const getSelectedValue = (input: SelectedValueProps) => { - const { selected = [], multiple, key } = input + let { selected = [], multiple, key } = input if (multiple || isEmpty(selected)) return '' return selected[0][key] || '' } const Input = (props: SelectInputProps) => { - const { + let { disabled, inputClass, icons = {}, @@ -40,16 +40,16 @@ const Input = (props: SelectInputProps) => { height } = props - const isSmallInput = inputSize === 'small' - const isLargeInput = inputSize === 'large' - const hasLeftIcon = !isEmpty(icons.left.component) - const hasRightIcon = !isEmpty(icons.right.component) && !disabled - const value = getSelectedValue({ selected, multiple, key: labelKey }) - const showClearIcon = (allowClear && !disabled) && (multiple ? !isEmpty(selected) : !isEmpty(value)) - const inputEle = useRef(null) - const isTextOnlyAndBorderLess = textOnly && borderless + let isSmallInput = inputSize === 'small' + let isLargeInput = inputSize === 'large' + let hasLeftIcon = !isEmpty(icons.left.component) + let hasRightIcon = !isEmpty(icons.right.component) && !disabled + let value = getSelectedValue({ selected, multiple, key: labelKey }) + let showClearIcon = (allowClear && !disabled) && (multiple ? !isEmpty(selected) : !isEmpty(value)) + let inputEle = useRef(null) + let isTextOnlyAndBorderLess = textOnly && borderless - const inputClassHash = { + let inputClassHash = { 'has-left-icon': hasLeftIcon, 'has-right-icon': hasRightIcon, 'has-clear-icon': showClearIcon, @@ -61,15 +61,15 @@ const Input = (props: SelectInputProps) => { 'ui-kit-select-input_lg': isLargeInput } - const renderValue = () => multiple ? renderTags() : {value} + let renderValue = () => multiple ? renderTags() : {value} - const renderTags = () => selected.map((_selected, i) => ( + let renderTags = () => selected.map((_selected, i) => ( onRemove(_selected)} key={i}> {_selected.__label} )) - const style: CSSProperties = {} + let style: CSSProperties = {} if (isTextOnlyAndBorderLess) { style.width = 'auto' @@ -120,7 +120,7 @@ const Input = (props: SelectInputProps) => { } const InputWrapper = (props: SelectInputProps) => { - const { + let { icons = {}, disabled, loading, @@ -134,11 +134,11 @@ const InputWrapper = (props: SelectInputProps) => { textOnly } = props - const isSmallInput = inputSize === 'small' - const isLargeInput = inputSize === 'large' + let isSmallInput = inputSize === 'small' + let isLargeInput = inputSize === 'large' - const hasLeftIcon = !isEmpty(icons.left.component) - const hasRightIcon = !isEmpty(icons.right.component) && !disabled + let hasLeftIcon = !isEmpty(icons.left.component) + let hasRightIcon = !isEmpty(icons.right.component) && !disabled let iconClass = '' if (isSmallInput || (borderless && textOnly)) { @@ -147,13 +147,13 @@ const InputWrapper = (props: SelectInputProps) => { iconClass = 'ui-kit-select-input-icon-lg' } - const value = isEmpty(selected) ? '' : getSelectedValue({ selected, multiple, key: labelKey }) + let value = isEmpty(selected) ? '' : getSelectedValue({ selected, multiple, key: labelKey }) - const onRightIconClick = (disabled || !hasRightIcon) ? noop : () => icons.right.onClick() + let onRightIconClick = (disabled || !hasRightIcon) ? noop : () => icons.right.onClick() - const showClearIcon = (allowClear && !disabled) && (multiple ? !isEmpty(selected) : !isEmpty(value)) + let showClearIcon = (allowClear && !disabled) && (multiple ? !isEmpty(selected) : !isEmpty(value)) - const renderRightIcon = () => { + let renderRightIcon = () => { if (loading) { return (
diff --git a/src/TextInput/index.tsx b/src/TextInput/index.tsx index 4d958ff..f827082 100644 --- a/src/TextInput/index.tsx +++ b/src/TextInput/index.tsx @@ -1,6 +1,5 @@ import React, { InputHTMLAttributes, MouseEvent, useRef, forwardRef, useImperativeHandle, useEffect, useState } from 'react' import cx from 'classnames' -// import Tooltip from '../Tooltip' import { TextInputProps, NumberFieldProps } from './props' import InfoCircle from '../icons/info-circle' import Error from '../icons/error' @@ -147,6 +146,11 @@ const TextInput = (props: InputHTMLAttributes(null) let [hasLeftIcon, setLeftIcon] = useState(false) diff --git a/src/scss/_variables.scss b/src/scss/_variables.scss index ece08ca..6268edb 100644 --- a/src/scss/_variables.scss +++ b/src/scss/_variables.scss @@ -71,6 +71,7 @@ $info-100: lighten($info, 98% - lightness($info)); $border-color: var(--border-color, #d8d8d8); $hover-border-color: var(--hover-border-color, #e8e8e8); $hover-bg: var(--hover-bg, #f3f5f7); +$hover-bg-light: var(--hover-bg-light, #f8f9fa); $disabled: var(--disabled-bg, #f4f7fb); $disabled-text: var(--disabled-text, lighten(#000, 70%)); $input-icon-width: 36px; diff --git a/src/scss/accordion.scss b/src/scss/accordion.scss index 54ed006..427f17f 100644 --- a/src/scss/accordion.scss +++ b/src/scss/accordion.scss @@ -13,7 +13,7 @@ &:hover, &.is-active { - background-color: $hover-bg; + background-color: $hover-bg-light; } &.is-active { diff --git a/src/scss/alert.scss b/src/scss/alert.scss index f1f7a37..f905f2e 100644 --- a/src/scss/alert.scss +++ b/src/scss/alert.scss @@ -33,6 +33,18 @@ border-left-width: 4px; background-color: $white; + &-title { + font-weight: 600; + } + + &-title + &-description { + margin-top: 4px; + } + + &-description { + color: $text-grey; + } + &.ui-kit-alert-success { color: $alert-text-color; border-color: $success; diff --git a/src/scss/common.scss b/src/scss/common.scss index b531f95..1f2cccf 100644 --- a/src/scss/common.scss +++ b/src/scss/common.scss @@ -33,6 +33,29 @@ .form-fields { padding: 0 16px; + + &-groupped { + display: flex; + margin-bottom: 16px; + + &.groupped-row { + .ui-kit-input-block { + margin-bottom: 0; + + &:not(:last-child) { + margin-right: 16px; + } + } + } + + &.groupped-column { + flex-direction: column; + + .ui-kit-input-block:last-child { + margin-bottom: 0; + } + } + } } } diff --git a/src/scss/dialog.scss b/src/scss/dialog.scss index 6dcc768..364e19a 100644 --- a/src/scss/dialog.scss +++ b/src/scss/dialog.scss @@ -15,6 +15,11 @@ .ui-kit-dialog-close { width: 24px; height: 24px; + transition: all .2s ease-in; + + &:hover { + box-shadow: 0 0 2px 1px inset rgba(0, 0, 0, .2); + } } } diff --git a/src/scss/select.scss b/src/scss/select.scss index c322507..577f9b4 100644 --- a/src/scss/select.scss +++ b/src/scss/select.scss @@ -169,7 +169,7 @@ $translateY: 50%; } .has-error { - .ui-kit-select-input { + .ui-kit-select-input:not(.sub-select-input) { border-color: $danger; &:hover{