feat(core-blocks): RadioGroup component

This commit is contained in:
Wroud
2020-09-06 23:53:11 +03:00
parent d37d386e5f
commit 31eee36801
4 changed files with 182 additions and 6 deletions
@@ -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<HTMLInputElement> & {
type BaseProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, '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<TKey extends keyof TState, TState> = BaseProps & {
name: TKey;
value: TState[TKey];
state: TState;
onChange?(value: TState[TKey], name: TKey): any;
checked?: never;
}
type RadioType = {
(props: ControlledProps): JSX.Element;
<TKey extends keyof TState, TState>(props: ObjectProps<TKey, TState>): 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<any, any>) {
const formContext = useContext(FormContext);
const context = useContext(RadioGroupContext);
const name = context?.name || controlledName;
const handleChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
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])))(
<field as="div" className={className}>
<radio as="div">
<input type="radio" id={id} {...rest}/>
<input
{...rest}
type="radio"
id={id}
name={name}
value={value}
checked={checked}
onChange={handleChange}
/>
<radio-background as="div">
<radio-outer-circle as="div"/>
<radio-inner-circle as="div"/>
@@ -76,4 +152,4 @@ export function Radio({
<label htmlFor={id}>{children}</label>
</field>
);
}
});
@@ -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<T> = BaseProps & {
value?: T;
onChange?(value: T, name: string): any;
state?: never;
}
type ObjectProps<TKey extends keyof TState, TState> = BaseProps & {
name: TKey;
state: TState;
onChange?(value: TState[TKey], name: TKey): any;
value?: never;
}
type RadioGroupType = {
<T>(props: ControlledProps<T>): JSX.Element;
<TKey extends keyof TState, TState>(props: ObjectProps<TKey, TState>): JSX.Element;
}
export const RadioGroup: RadioGroupType = observer(function RadioGroup({
name,
value: controlledValue,
state,
onChange,
children,
}: ControlledProps<string | number> | ObjectProps<any, any>) {
const formContext = useContext(FormContext);
const [selfValue, setValue] = useState<string | number>();
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 (
<RadioGroupContext.Provider value={context}>
{children}
</RadioGroupContext.Provider>
);
});
@@ -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<IRadioGroupContext | undefined>(undefined);
+2
View File
@@ -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';