From 31eee3680102bff57d681fc19544f20a23bf7c4e Mon Sep 17 00:00:00 2001 From: Wroud Date: Sun, 6 Sep 2020 23:53:11 +0300 Subject: [PATCH] feat(core-blocks): RadioGroup component --- .../core-blocks/src/FormControls/Radio.tsx | 88 +++++++++++++++++-- .../src/FormControls/RadioGroup.tsx | 81 +++++++++++++++++ .../src/FormControls/RadioGroupContext.ts | 17 ++++ webapp/packages/core-blocks/src/index.ts | 2 + 4 files changed, 182 insertions(+), 6 deletions(-) create mode 100644 webapp/packages/core-blocks/src/FormControls/RadioGroup.tsx create mode 100644 webapp/packages/core-blocks/src/FormControls/RadioGroupContext.ts diff --git a/webapp/packages/core-blocks/src/FormControls/Radio.tsx b/webapp/packages/core-blocks/src/FormControls/Radio.tsx index 3ffd16a3ca..41bbedda1b 100644 --- a/webapp/packages/core-blocks/src/FormControls/Radio.tsx +++ b/webapp/packages/core-blocks/src/FormControls/Radio.tsx @@ -6,10 +6,15 @@ * you may not use this file except in compliance with the License. */ +import { observer } from 'mobx-react'; +import { useContext, useCallback } from 'react'; import styled, { css } from 'reshadow'; import { useStyles, composes } from '@cloudbeaver/core-theming'; +import { FormContext } from './FormContext'; +import { RadioGroupContext } from './RadioGroupContext'; + const radioStyles = composes( css` radio { @@ -52,21 +57,92 @@ const radioMod = { ), }; -type RadioProps = React.InputHTMLAttributes & { +type BaseProps = Omit, 'onChange' | 'value' | 'checked'> & { mod?: (keyof typeof radioMod)[]; } -export function Radio({ +type ControlledProps = BaseProps & { + value?: string | number; + checked?: boolean; + onChange?(value: string | number, name: string): any; + + state?: never; +} + +type ObjectProps = BaseProps & { + name: TKey; + value: TState[TKey]; + state: TState; + onChange?(value: TState[TKey], name: TKey): any; + + checked?: never; +} + +type RadioType = { + (props: ControlledProps): JSX.Element; + (props: ObjectProps): JSX.Element; +} + +export const Radio: RadioType = observer(function Radio({ + name: controlledName, + value, + state, + id: controlledId, + checked: controlledChecked, + onChange, mod, - id, className, children, ...rest -}: RadioProps) { +}: ControlledProps | ObjectProps) { + const formContext = useContext(FormContext); + const context = useContext(RadioGroupContext); + + const name = context?.name || controlledName; + + const handleChange = useCallback((event: React.ChangeEvent) => { + if (!event.target.checked) { + return; + } + + if (state) { + state[name] = value; + } + + if (context) { + context.onChange(value); + } else if (formContext) { + formContext.onChange(value, name); + } + + if (onChange) { + onChange(value, name); + } + }, [value, context, state, name, formContext, onChange]); + + const id = controlledId ?? `${name}_${value}`; + let checked = controlledChecked; + + if (context) { + checked = context.value === value; + } + + if (state) { + checked = state[name] === value; + } + return styled(useStyles(radioStyles, ...(mod || []).map(mod => radioMod[mod])))( - + @@ -76,4 +152,4 @@ export function Radio({ ); -} +}); diff --git a/webapp/packages/core-blocks/src/FormControls/RadioGroup.tsx b/webapp/packages/core-blocks/src/FormControls/RadioGroup.tsx new file mode 100644 index 0000000000..0d6b233f95 --- /dev/null +++ b/webapp/packages/core-blocks/src/FormControls/RadioGroup.tsx @@ -0,0 +1,81 @@ +/* + * cloudbeaver - Cloud Database Manager + * Copyright (C) 2020 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +import { observer } from 'mobx-react'; +import { + useCallback, useMemo, useState, useContext +} from 'react'; + +import { FormContext } from './FormContext'; +import { RadioGroupContext, IRadioGroupContext } from './RadioGroupContext'; + +type BaseProps = React.PropsWithChildren<{ + name: string; +}> + +type ControlledProps = BaseProps & { + value?: T; + onChange?(value: T, name: string): any; + + state?: never; +} + +type ObjectProps = BaseProps & { + name: TKey; + state: TState; + onChange?(value: TState[TKey], name: TKey): any; + + value?: never; +} + +type RadioGroupType = { + (props: ControlledProps): JSX.Element; + (props: ObjectProps): JSX.Element; +} + +export const RadioGroup: RadioGroupType = observer(function RadioGroup({ + name, + value: controlledValue, + state, + onChange, + children, +}: ControlledProps | ObjectProps) { + const formContext = useContext(FormContext); + const [selfValue, setValue] = useState(); + + const handleChange = useCallback((value: string | number) => { + if (state) { + state[name] = value; + } else { + setValue(value); + } + + if (onChange) { + onChange(value, name); + } + + if (formContext) { + formContext.onChange(value, name); + } + + }, [name, state, formContext, onChange]); + + const value = state ? state[name] : controlledValue ?? selfValue; + + const context: IRadioGroupContext = useMemo(() => ({ + name, + value, + onChange: handleChange, + }), [value, value, handleChange]); + + return ( + + {children} + + ); +}); diff --git a/webapp/packages/core-blocks/src/FormControls/RadioGroupContext.ts b/webapp/packages/core-blocks/src/FormControls/RadioGroupContext.ts new file mode 100644 index 0000000000..01534af826 --- /dev/null +++ b/webapp/packages/core-blocks/src/FormControls/RadioGroupContext.ts @@ -0,0 +1,17 @@ +/* + * cloudbeaver - Cloud Database Manager + * Copyright (C) 2020 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +import { createContext } from 'react'; + +export interface IRadioGroupContext { + value: string | number | undefined; + name: string; + onChange(value: string| number): any; +} + +export const RadioGroupContext = createContext(undefined); diff --git a/webapp/packages/core-blocks/src/index.ts b/webapp/packages/core-blocks/src/index.ts index 6b5226b8c9..296d82253c 100644 --- a/webapp/packages/core-blocks/src/index.ts +++ b/webapp/packages/core-blocks/src/index.ts @@ -19,6 +19,8 @@ export * from './FormControls/FormContext'; export * from './FormControls/InputField'; export * from './FormControls/InputGroup'; export * from './FormControls/Radio'; +export * from './FormControls/RadioGroup'; +export * from './FormControls/RadioGroupContext'; export * from './FormControls/ShadowInput'; export * from './FormControls/SubmittingForm'; export * from './FormControls/Switch';