Skip to content

Commit

Permalink
GC-30; (Feat) Add Radio and RadioGroup components
Browse files Browse the repository at this point in the history
  • Loading branch information
Yana Korpan authored and YanaKorpan committed Sep 15, 2023
1 parent a16f2f9 commit 5517fe9
Show file tree
Hide file tree
Showing 13 changed files with 205 additions and 7 deletions.
42 changes: 42 additions & 0 deletions admiral/form/fields/RadioInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React, { useCallback } from 'react'
import { useForm } from '../FormContext'
import { Form } from '../Form'
import { RadioGroup } from '../../ui'
import { FormItemProps } from '../Item'
import type { RadioGroupProps } from '../../ui/Radio/interfaces'
import { InputComponentWithName } from '../interfaces'

export interface RadioInputProps extends RadioGroupProps, FormItemProps {
name: string
}

export const RadioInput: InputComponentWithName<React.FC<RadioInputProps>> = ({
name,
label,
required,
columnSpan,
...inputProps
}) => {
const { values, errors, options, setValues } = useForm()
const value = values[name]
const error = errors[name]?.[0]
const opts = options[name]

const onChange = useCallback((e) => {
setValues((values: any) => ({ ...values, [name]: e.target.value }))
}, [])

return (
<Form.Item label={label} required={required} error={error} columnSpan={columnSpan}>
<RadioGroup
options={opts}
{...inputProps}
name={name}
value={value}
onChange={onChange}
/>
</Form.Item>
)
}

RadioInput.inputName = 'RadioInput'
1 change: 1 addition & 0 deletions admiral/form/fields/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ export * from './SlugInput'
export * from './TextInput'
export * from './TimePickerInput'
export * from './TranslatableInput'
export * from './RadioInput'
1 change: 1 addition & 0 deletions admiral/form/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export const INPUT_NAMES = {
slug: 'SlugInput',
translatable: 'TranslatableInput',
ajaxSelectInput: 'AjaxSelectInput',
radio: 'RadioInput',
} as const

const inputTypes = tuple(...Object.values(INPUT_NAMES))
Expand Down
4 changes: 2 additions & 2 deletions admiral/ui/Checkbox/interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ChoiceChangeEvent } from '../Choice/interfaces'
import { ChoiceChangeEvent, ChoiceView } from '../Choice/interfaces'

export interface AbstractCheckboxProps<T> {
className?: string
Expand All @@ -20,7 +20,7 @@ export interface AbstractCheckboxProps<T> {
}

export interface CheckboxProps extends AbstractCheckboxProps<ChoiceChangeEvent> {
view?: 'primary' | 'ghost'
view?: ChoiceView
size?: 'm' | 'l'
align?: 'top' | 'center' | 'bottom'
indeterminate?: boolean
Expand Down
6 changes: 6 additions & 0 deletions admiral/ui/Choice/Choice.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@
height: 50%;
border-width: 0px;
background: currentColor;
transform: scale(0.4);
}
}
.input:checked + .inner {
&::after {
transform: scale(1);
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion admiral/ui/Choice/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ export interface ChoiceChangeEvent {
event: Event
}

export type ChoiceView = 'primary' | 'ghost'

export interface ChoiceProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange'> {
classNames?: { wrapper?: string; input?: string; inner?: string }
view?: 'primary' | 'ghost'
view?: ChoiceView
indeterminate?: boolean
onChange?: (e: ChoiceChangeEvent) => void
}
5 changes: 5 additions & 0 deletions admiral/ui/Radio/Radio.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.radio__Group {
> *:not(:last-of-type) {
margin-right: var(--space-s);
}
}
17 changes: 17 additions & 0 deletions admiral/ui/Radio/Radio.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React, { forwardRef, memo } from 'react'
import { Checkbox } from '../Checkbox/'
import { RadioProps } from './interfaces'

const InternalRadio: React.ForwardRefRenderFunction<HTMLInputElement, RadioProps> = (
{ children, ...restProps },
ref,
) => {
return (
<Checkbox ref={ref} type="radio" {...restProps}>
{children}
</Checkbox>
)
}

const Radio = forwardRef<HTMLInputElement, RadioProps>(InternalRadio)
export default memo(Radio) as typeof Radio
100 changes: 100 additions & 0 deletions admiral/ui/Radio/RadioGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React, { forwardRef, memo, useCallback, useEffect, useState } from 'react'
import cn from 'classnames'
import Radio from './Radio'
import { ChoiceChangeEvent } from '../Choice/interfaces'
import { RadioGroupProps } from './interfaces'
import styles from './Radio.module.scss'

const InternalRadioGroup: React.ForwardRefRenderFunction<HTMLDivElement, RadioGroupProps> = (
props,
ref,
) => {
const {
options = [],
defaultValue,
onChange,
className,
style,
disabled,
value: valueFromProps,
onMouseEnter,
onMouseLeave,
onFocus,
onBlur,
...restProps
} = props

const [value, setValue] = useState(
typeof valueFromProps === 'undefined' ? defaultValue : valueFromProps,
)

useEffect(() => {
if (valueFromProps !== undefined || value !== valueFromProps) {
setValue(valueFromProps)
}
}, [valueFromProps])

const _onChange = useCallback(
(e: ChoiceChangeEvent) => {
setValue(e.target.value)

onChange?.(e)
},
[onChange],
)

let childrenNode: React.ReactNode

if (options && options.length > 0) {
childrenNode = options.map((option) => {
if (typeof option === 'string' || typeof option === 'number') {
return (
<Radio
key={`radio-value-${option}`}
disabled={disabled}
value={option}
defaultChecked={defaultValue == option}
checked={value === option}
type="radio"
onChange={_onChange}
{...restProps}
>
{option}
</Radio>
)
}

return (
<Radio
key={`radio-value-${option.value}`}
disabled={disabled}
value={option.value}
defaultChecked={defaultValue == option.value}
checked={value === option.value}
type="radio"
onChange={_onChange}
{...restProps}
>
{option.label}
</Radio>
)
})
}

return (
<div
className={cn(styles.radio__Group, className)}
style={style}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onFocus={onFocus}
onBlur={onBlur}
ref={ref}
>
{childrenNode}
</div>
)
}

const RadioGroup = forwardRef<HTMLDivElement, RadioGroupProps>(InternalRadioGroup)
export default memo(RadioGroup) as typeof RadioGroup
2 changes: 2 additions & 0 deletions admiral/ui/Radio/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as Radio } from './Radio'
export { default as RadioGroup } from './RadioGroup'
23 changes: 23 additions & 0 deletions admiral/ui/Radio/interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { CheckboxProps } from '../Checkbox/interfaces'

export interface RadioProps extends CheckboxProps {}

export type RadioValueType = string | number | null

export interface RadioOptionType {
label: React.ReactNode
value: RadioValueType
}

export interface RadioGroupProps
extends Omit<
RadioProps,
'defaultChecked' | 'checked' | 'type' | 'autofocus' | 'children' | 'id'
> {
options?: Array<RadioOptionType | string | number>
defaultValue?: any
onMouseEnter?: React.MouseEventHandler<HTMLDivElement>
onMouseLeave?: React.MouseEventHandler<HTMLDivElement>
onFocus?: React.FocusEventHandler<HTMLDivElement>
onBlur?: React.FocusEventHandler<HTMLDivElement>
}
1 change: 1 addition & 0 deletions admiral/ui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export * from './Notification'
export * from './Page'
export * from './Pagination'
export * from './Popconfirm'
export * from './Radio'
export * from './Select'
export * from './Spin'
export * from './Switch'
Expand Down
6 changes: 2 additions & 4 deletions src/crud/users/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
DatePickerInput,
AjaxSelectInput,
SlugInput,
RadioInput,
} from '../../../admiral'
import api from '../../api'

Expand Down Expand Up @@ -128,10 +129,7 @@ export const UsersCRUD = createCRUD({
Project managers
</SelectInput.Option>
</SelectInput>
<SelectInput label="Role" name="role" placeholder="Choose Role" required>
<SelectInput.Option value="accountant">Accountant</SelectInput.Option>
<SelectInput.Option value="recruiter">HR Officer</SelectInput.Option>
</SelectInput>
<RadioInput label="Role" name="role" required />
<FilePictureInput
columnSpan={2}
label="Avatar"
Expand Down

0 comments on commit 5517fe9

Please sign in to comment.