Skip to content

Commit

Permalink
feat: update fields, state, naming
Browse files Browse the repository at this point in the history
  • Loading branch information
Vitaliy Polyanskiy committed Aug 18, 2020
1 parent 01324e2 commit b5be2aa
Show file tree
Hide file tree
Showing 52 changed files with 381 additions and 372 deletions.
2 changes: 1 addition & 1 deletion src/api/github/modules/repositories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { GithubRepositoryData } from '@app/models/github-repository'

export type GetGithubRepositoriesResponseDTO = GithubRepositoryData[]

export const getRepositories = () =>
export const getItems = () =>
githubHttpClient<GetGithubRepositoriesResponseDTO>({
url: '/repositories'
})
2 changes: 1 addition & 1 deletion src/api/github/modules/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { GithubUserData } from '@app/models/github-user'

export type GetGithubUsersResponseDTO = GithubUserData[]

export const getUsers = () =>
export const getItems = () =>
githubHttpClient<GetGithubUsersResponseDTO>({
url: '/users'
})
4 changes: 2 additions & 2 deletions src/components/bootstrap/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { GlobalHooksSetuper } from '@app/components/global-hooks'
import { localesActions } from '@app/state/modules/locales'
import { useShallowEqualSelector } from '@app/hooks/store/use-shallow-equal-selector'
import { useUserData } from '@app/hooks/features/use-user-data'
import { Loader } from '@app/components/ui/loader'
import { UiLoader } from '@app/components/ui/loader'
import { userActions } from '@app/state/modules/user'

interface Props {
Expand All @@ -31,7 +31,7 @@ export function Bootstrap({ type = 'external', localeNamespaces, children }: Pro
dispatch(localesActions.getMessages(localeNamespaces))
}, [dispatch, localeNamespaces])

if ((type === 'account' && !userInfo) || !messages) return <Loader loading />
if ((type === 'account' && !userInfo) || !messages) return <UiLoader loading />

return (
<IntlProvider locale={locale} messages={messages} wrapRichTextChunksInFragment>
Expand Down
14 changes: 7 additions & 7 deletions src/components/toasters/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useShallowEqualSelector } from '@app/hooks/store/use-shallow-equal-selector'
import { Toaster } from '@app/components/ui/toaster'
import { Alert } from '@app/components/ui/alert'
import { UiToaster } from '@app/components/ui/toaster'
import { UiAlert } from '@app/components/ui/alert'
import { useCallback, SyntheticEvent } from 'react'
import { useDispatch } from 'react-redux'
import { eventsActions } from '@app/state/modules/events'
Expand All @@ -15,18 +15,18 @@ export function Toasters() {
const handleClose = useCallback(
(id: string) => (_: SyntheticEvent, reason?: string) => {
if (reason === 'clickaway') return
dispatch(eventsActions.removeEvent(id))
dispatch(eventsActions.remove(id))
},
[dispatch]
)
return (
<>
{events.map((item, index) => (
<Toaster key={index} open autoHideDuration={AUTO_HIDE_DURATION} onClose={handleClose(item.id)}>
<Alert onClose={handleClose(item.id)} severity={item.type}>
<UiToaster key={index} open autoHideDuration={AUTO_HIDE_DURATION} onClose={handleClose(item.id)}>
<UiAlert onClose={handleClose(item.id)} severity={item.type}>
{stringify(item.message)}
</Alert>
</Toaster>
</UiAlert>
</UiToaster>
))}
</>
)
Expand Down
8 changes: 3 additions & 5 deletions src/components/ui/alert/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import _Alert, { AlertProps as _AlertProps } from '@material-ui/lab/Alert'
import Alert, { AlertProps } from '@material-ui/lab/Alert'

type AlertProps = _AlertProps

export function Alert({ elevation = 6, variant = 'filled', ...props }: AlertProps) {
return <_Alert elevation={elevation} variant={variant} {...props} />
export function UiAlert({ elevation = 6, variant = 'filled', ...props }: AlertProps) {
return <Alert elevation={elevation} variant={variant} {...props} />
}
3 changes: 3 additions & 0 deletions src/components/ui/fields/common.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.error {
@mixin input-error-text;
}
14 changes: 14 additions & 0 deletions src/components/ui/fields/common.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import _TextField, { TextFieldProps } from '@material-ui/core/TextField'
import styles from './common.css'

export const commonTextInputProps: UiTextFieldProps = {
fullWidth: true,
variant: 'outlined',
FormHelperTextProps: {
className: styles.error
}
}

export type UiTextFieldProps = TextFieldProps & {
maxLength?: number
}
27 changes: 27 additions & 0 deletions src/components/ui/fields/selects/interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { ReactNode } from 'react'
import type { SelectProps } from '@material-ui/core/Select'

type SelectFieldItem = {
value: string
name: string
}

export type SelectFieldItems = SelectFieldItem[]

interface Props {
items: SelectFieldItems
helperText?: ReactNode
theme?: SelectTheme
hasPlaceholder?: boolean
renderItem?: (item: SelectFieldItem) => ReactNode
renderValue?: (items: SelectFieldItems) => ReactNode
}

export type SelectFieldProps = Props & Omit<SelectProps, 'children'>

export interface SelectTheme {
select?: string
input?: string
item?: string
error?: string
}
66 changes: 29 additions & 37 deletions src/components/ui/fields/selects/select/index.tsx
Original file line number Diff line number Diff line change
@@ -1,67 +1,59 @@
import { ReactNode, useMemo } from 'react'
import Select, { SelectProps } from '@material-ui/core/Select'
import { useMemo } from 'react'
import Select from '@material-ui/core/Select'
import MenuItem from '@material-ui/core/MenuItem'
import FormControl from '@material-ui/core/FormControl'
import FormHelperText from '@material-ui/core/FormHelperText/FormHelperText'
import { Label } from '@app/components/ui/fields/label'
import cn from 'classnames'
import styles from './styles.css'
import { useFieldHandleChange } from '@app/hooks/forms/use-field-handle-change'

export type SelectFieldsItems = {
value: string | number
name: ReactNode
}[]

interface Props {
items?: SelectFieldsItems
helperText?: ReactNode
}

export type SelectFieldProps = Props & SelectProps
import { SelectFieldProps, SelectFieldItems } from '@app/components/ui/fields/selects/interfaces'
import { useMergeTheme } from '@app/hooks/use-merge-theme'

export function SelectField({
items,
children,
label,
className,
error,
helperText,
variant = 'outlined',
inputProps,
value: fieldValue,
onChange,
theme,
hasPlaceholder,
renderItem = defaultRenderItem,
renderValue,
...props
}: SelectFieldProps) {
const { value, handleChange } = useFieldHandleChange({ fieldValue, onChange })
const _theme = useMergeTheme(styles, theme)
const _inputProps = useMemo(() => {
return {
...inputProps,
className: cn(styles.input, props.displayEmpty && !value && styles.placeholder, inputProps?.className)
className: cn(_theme.input, hasPlaceholder && !props.value && styles.placeholder)
}
}, [inputProps, props.displayEmpty, value])
const _children = useMemo(() => {
return items
? items.map(item => (
<MenuItem
key={item.value}
value={item.value}
className={styles.item}
disabled={item.value === ''}
children={item.name}
/>
))
: children
}, [items, children])
}, [inputProps, hasPlaceholder, props.value, _theme.input])
const renderedItems = useMemo(() => {
return items.map(item => (
<MenuItem
key={item.value}
value={item.value}
className={_theme.item}
disabled={item.value === ''}
children={renderItem(item)}
/>
))
}, [items, renderItem, _theme.item])
return (
<>
<Label>{label}</Label>
<FormControl fullWidth variant={variant} className={cn(styles.select, className)} error={error}>
<Select {...props} inputProps={_inputProps} value={value} onChange={handleChange}>
{_children}
<FormControl fullWidth variant={variant} className={_theme.select} error={error}>
<Select {...props} inputProps={_inputProps} displayEmpty={hasPlaceholder} renderValue={renderValue}>
{renderedItems}
</Select>
{error && <FormHelperText error={error}>{helperText}</FormHelperText>}
</FormControl>
</>
)
}

function defaultRenderItem(item: SelectFieldItems[0]) {
return item.name
}
29 changes: 29 additions & 0 deletions src/components/ui/fields/text-field/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useMemo } from 'react'
import TextField from '@material-ui/core/TextField'
import { Label } from '@app/components/ui/fields/label'
import { commonTextInputProps, UiTextFieldProps } from '@app/components/ui/fields/common'
import cn from 'classnames'
import styles from './styles.css'

export function UiTextField({ className, label, inputProps, multiline, maxLength, ...props }: UiTextFieldProps) {
const _inputProps = useMemo<UiTextFieldProps['inputProps']>(
() => ({
...inputProps,
className: cn(styles.input, multiline && styles.multiline, inputProps?.className),
maxLength
}),
[inputProps, multiline, maxLength]
)
return (
<>
<Label>{label}</Label>
<TextField
{...commonTextInputProps}
{...props}
multiline={multiline}
className={cn(styles.field, className)}
inputProps={_inputProps}
/>
</>
)
}
40 changes: 0 additions & 40 deletions src/components/ui/fields/text-input/index.tsx

This file was deleted.

5 changes: 5 additions & 0 deletions src/components/ui/form/hooks/use-form-context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { useFormikContext } from 'formik'

export function useFormContext<T>() {
return useFormikContext<T>()
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import { getIn, FormikProps, FieldInputProps } from 'formik'
import { useMemo } from 'react'
import { camelCase2Dash } from '@app/utils/format'
import { TProps } from '@app/components/t'
import { camelCaseToKebabCase } from '@app/utils/format'
import { TProps, T } from '@app/components/t'

type ErrorTextIdType = 'string' | { rid: string; values?: TProps['values'] } | undefined

export function useErrorTextProps<F, I>(form: FormikProps<F>, field: FieldInputProps<I>) {
return useMemo(() => {
export function useFormFieldErrorProps<F, I>(form: FormikProps<F>, field: FieldInputProps<I>) {
const errorTextProps = useMemo(() => {
const errorTextProps: ErrorTextIdType = getIn(form.touched, field.name) && getIn(form.errors, field.name)
if (!errorTextProps) return errorTextProps
const props = typeof errorTextProps === 'string' ? { rid: errorTextProps } : errorTextProps
return {
...props,
id: camelCase2Dash(props.rid)
id: camelCaseToKebabCase(props.rid),
}
}, [form.touched, form.errors, field.name])

return { helperText: errorTextProps && <T {...errorTextProps} />, error: !!errorTextProps }
}
38 changes: 29 additions & 9 deletions src/components/ui/form/index.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,40 @@
import { PropsWithChildren } from 'react'
import { Formik, Form as _Form, FormikConfig } from 'formik'
import { PropsWithChildren, useCallback } from 'react'
import { Formik, Form as _Form, FormikConfig, FormikHelpers } from 'formik'
import { FormFocusError } from '@app/components/ui/form/form-focus-error'
import { UiLoader } from '@app/components/ui/loader'

interface Props {
className?: string
loading?: boolean
hasCast?: boolean
}

export type FormWrapperProps<T> = PropsWithChildren<FormikConfig<T> & Props>
export type FormHelpers<T = unknown> = FormikHelpers<T>

export function FormWrapper<Values>({ children, className, ...props }: FormWrapperProps<Values>) {
export function FormWrapper<Values>({
children,
className,
loading,
onSubmit,
validationSchema,
hasCast,
...props
}: FormWrapperProps<Values>) {
const handleSubmit = useCallback(
(values: Values, helpers: FormikHelpers<Values>) => {
onSubmit(hasCast && validationSchema ? validationSchema.cast(values) : values, helpers)
},
[hasCast, onSubmit, validationSchema]
)
return (
<Formik {...props}>
<_Form className={className}>
{children}
<FormFocusError />
</_Form>
</Formik>
<UiLoader loading={loading} className={className}>
<Formik {...props} validationSchema={validationSchema} onSubmit={handleSubmit}>
<_Form>
{children}
<FormFocusError />
</_Form>
</Formik>
</UiLoader>
)
}

0 comments on commit b5be2aa

Please sign in to comment.