feat(components): add InlineWrapper and related styles for improved form handling
This commit is contained in:
176
src/FormerControllers/Inputs/InlineWrapper.tsx
Normal file
176
src/FormerControllers/Inputs/InlineWrapper.tsx
Normal file
@@ -0,0 +1,176 @@
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
import {
|
||||
Box,
|
||||
Center,
|
||||
Flex,
|
||||
type FlexProps,
|
||||
Paper,
|
||||
Stack,
|
||||
Title,
|
||||
type TitleProps,
|
||||
Tooltip,
|
||||
useMantineColorScheme,
|
||||
} from '@mantine/core'
|
||||
import React from 'react'
|
||||
|
||||
import classes from './InlineWapper.module.css'
|
||||
|
||||
interface InlineWrapperCallbackProps extends Partial<InlineWrapperPropsOnly> {
|
||||
classNames: React.CSSProperties
|
||||
dataCssProps?: Record<string, any>
|
||||
size: string
|
||||
}
|
||||
|
||||
interface InlineWrapperProps extends InlineWrapperPropsOnly{
|
||||
children?: ((props: InlineWrapperCallbackProps) => ReactNode) | ReactNode
|
||||
}
|
||||
interface InlineWrapperPropsOnly {
|
||||
error?: ReactNode | string
|
||||
flexProps?: FlexProps
|
||||
label: ReactNode | string
|
||||
labelProps?: TitleProps
|
||||
promptArea?: ((props: InlineWrapperCallbackProps) => ReactNode) | ReactNode
|
||||
promptWidth?: FlexProps['w']
|
||||
required?: boolean
|
||||
rightSection?: ((props: InlineWrapperCallbackProps) => ReactNode) | ReactNode
|
||||
styles?: React.CSSProperties
|
||||
tooltip?: string
|
||||
value?: any
|
||||
}
|
||||
|
||||
function InlineWrapper(props: InlineWrapperProps) {
|
||||
return (
|
||||
<Stack gap={0}>
|
||||
<Flex
|
||||
gap={0}
|
||||
h={undefined}
|
||||
m={0}
|
||||
mb={0}
|
||||
p={0}
|
||||
w={undefined}
|
||||
wrap='nowrap'
|
||||
{...props.flexProps}
|
||||
bg={'var(--input-background)'}
|
||||
>
|
||||
{props.promptWidth && props.promptWidth !== 0 ? <Prompt {...props} /> : null}
|
||||
<div
|
||||
style={{
|
||||
borderRadius: 0,
|
||||
flex: 10,
|
||||
}}
|
||||
>
|
||||
{typeof props.children === 'function' ? (
|
||||
props.children({ ...props, classNames: classes, size: 'xs' })
|
||||
) : typeof props.children === 'object' && React.isValidElement(props.children) ? (
|
||||
<props.children.type classNames={classes} size='xs' {...(typeof props.children.props === "object" ? props.children.props : {})} />
|
||||
) : (
|
||||
props.children
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!props.rightSection ? undefined : typeof props.rightSection === 'function' ? (
|
||||
props.rightSection({
|
||||
...props,
|
||||
classNames: classes,
|
||||
size: 'xs',
|
||||
})
|
||||
) : typeof props.rightSection === 'object' && React.isValidElement(props.rightSection) ? (
|
||||
<props.rightSection.type classNames={classes} size='xs' {...(typeof props.rightSection.props === "object" ? props.rightSection.props : {})} />
|
||||
) : (
|
||||
props.rightSection
|
||||
)}
|
||||
</Flex>
|
||||
{/* <ErrorComponent {...props} /> */}
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
function isValueEmpty(inputValue: any) {
|
||||
if (inputValue === null || inputValue === undefined) return true
|
||||
if (typeof inputValue === 'number') {
|
||||
if (inputValue === 0) return false
|
||||
} else if (typeof inputValue === 'string' || inputValue === '') {
|
||||
return inputValue.trim() === ''
|
||||
} else if (inputValue instanceof File) {
|
||||
return inputValue.size === 0
|
||||
} else if (inputValue.target) {
|
||||
return isValueEmpty(inputValue.target?.value)
|
||||
} else if (inputValue.constructor?.name === 'Date') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function Prompt(props: Partial<InlineWrapperProps>) {
|
||||
return (
|
||||
<>
|
||||
{props.tooltip ? (
|
||||
<Tooltip label={props.tooltip}>
|
||||
<PromptDetail {...props} />
|
||||
</Tooltip>
|
||||
) : (
|
||||
<PromptDetail {...props} />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function PromptDetail(props: Partial<InlineWrapperProps>) {
|
||||
const colors = useColors(props)
|
||||
return props.promptArea ? (
|
||||
<Box maw={props.promptWidth} w={'100%'}>
|
||||
{!props.promptArea ? undefined : typeof props.promptArea === 'function' ? (
|
||||
props.promptArea({
|
||||
...props,
|
||||
classNames: classes,
|
||||
dataCssProps: { 'data-promptArea': true },
|
||||
size: 'xs',
|
||||
})
|
||||
) : typeof props.rightSection === 'object' && React.isValidElement(props.promptArea) ? (
|
||||
<props.promptArea.type
|
||||
classNames={classes}
|
||||
data-promptArea='true'
|
||||
size='xs'
|
||||
{...(typeof props.promptArea?.props === "object" ? props.promptArea.props : {})}
|
||||
/>
|
||||
) : (
|
||||
props.promptArea
|
||||
)}
|
||||
</Box>
|
||||
) : (
|
||||
<Paper
|
||||
bg={colors.paperColor}
|
||||
className={classes.prompt}
|
||||
px='md'
|
||||
w={props.promptWidth}
|
||||
withBorder
|
||||
>
|
||||
<Center h='100%' style={{ justifyContent: 'start' }} w='100%'>
|
||||
<Title c={colors.titleColor} fz='xs' order={6} {...props.labelProps}>
|
||||
{props.label}
|
||||
{props.required && isValueEmpty(props.value) && <span style={{ color: 'red' }}>*</span>}
|
||||
</Title>
|
||||
</Center>
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
|
||||
function useColors(props: Partial<InlineWrapperProps>) {
|
||||
const { colorScheme } = useMantineColorScheme()
|
||||
|
||||
let titleColor = colorScheme === 'dark' ? 'dark.0' : 'gray.8'
|
||||
let paperColor = colorScheme === 'dark' ? 'dark.7' : 'gray.1'
|
||||
|
||||
if (props.required && isValueEmpty(props.value)) {
|
||||
paperColor = colorScheme === 'dark' ? '#413012e7' : 'yellow.1'
|
||||
}
|
||||
|
||||
if (props.error) {
|
||||
paperColor = colorScheme === 'dark' ? 'red.7' : 'red.0'
|
||||
titleColor = colorScheme === 'dark' ? 'red.0' : 'red.9'
|
||||
}
|
||||
return { paperColor, titleColor }
|
||||
}
|
||||
|
||||
export { InlineWrapper }
|
||||
export type { InlineWrapperProps }
|
||||
Reference in New Issue
Block a user