diff --git a/src/Former/stories/Gridler.goapi.stories.tsx b/src/Former/stories/Former.goapi.stories.tsx similarity index 100% rename from src/Former/stories/Gridler.goapi.stories.tsx rename to src/Former/stories/Former.goapi.stories.tsx diff --git a/src/FormerControllers/Inputs/InlineWapper.module.css b/src/FormerControllers/Inputs/InlineWapper.module.css new file mode 100644 index 0000000..ec63b25 --- /dev/null +++ b/src/FormerControllers/Inputs/InlineWapper.module.css @@ -0,0 +1,38 @@ +.prompt { + border-top-right-radius: 0px; + border-bottom-right-radius: 0px; + border: 1px solid #ced4da; + border-right: 0px; + + @mixin dark { + border: 1px solid #373a40; + } +} + +.input { + border: 1px solid #ced4da; + flex: 1; + + &:not([data-promptArea]) { + border-top-left-radius: 0px; + border-bottom-left-radius: 0px; + } + + &[data-promptArea] { + border-top-right-radius: 0px; + border-bottom-right-radius: 0px; + } + + &[data-disabled] { + color: black; + background-color: #fff; + } + + @mixin dark { + border: 1px solid #373a40; + } +} + +.root { + flex: 1; +} diff --git a/src/FormerControllers/Inputs/InlineWrapper.tsx b/src/FormerControllers/Inputs/InlineWrapper.tsx new file mode 100644 index 0000000..f19a06b --- /dev/null +++ b/src/FormerControllers/Inputs/InlineWrapper.tsx @@ -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 { + classNames: React.CSSProperties + dataCssProps?: Record + 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 ( + + + {props.promptWidth && props.promptWidth !== 0 ? : null} +
+ {typeof props.children === 'function' ? ( + props.children({ ...props, classNames: classes, size: 'xs' }) + ) : typeof props.children === 'object' && React.isValidElement(props.children) ? ( + + ) : ( + props.children + )} +
+ + {!props.rightSection ? undefined : typeof props.rightSection === 'function' ? ( + props.rightSection({ + ...props, + classNames: classes, + size: 'xs', + }) + ) : typeof props.rightSection === 'object' && React.isValidElement(props.rightSection) ? ( + + ) : ( + props.rightSection + )} +
+ {/* */} +
+ ) +} + +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) { + return ( + <> + {props.tooltip ? ( + + + + ) : ( + + )} + + ) +} + +function PromptDetail(props: Partial) { + const colors = useColors(props) + return props.promptArea ? ( + + {!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 + )} + + ) : ( + +
+ + {props.label} + {props.required && isValueEmpty(props.value) && <span style={{ color: 'red' }}>*</span>} + +
+
+ ) +} + +function useColors(props: Partial) { + 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 } diff --git a/src/FormerControllers/stories/Formers.goapi.stories.tsx b/src/FormerControllers/stories/Formers.goapi.stories.tsx new file mode 100644 index 0000000..d0b1941 --- /dev/null +++ b/src/FormerControllers/stories/Formers.goapi.stories.tsx @@ -0,0 +1,50 @@ +//@ts-nocheck +import type { Meta, StoryObj } from '@storybook/react-vite'; + +import { Stack } from '@mantine/core'; +import { fn } from 'storybook/test'; + +import { Former, NativeSelectCtrl, TextInputCtrl } from '../../lib'; +import { InlineWrapper } from '../Inputs/InlineWrapper'; +import NumberInputCtrl from '../Inputs/NumberInputCtrl'; + +const Renderable = () => { + return ( + + + + + + + + + + ); +}; + +const meta = { + // Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args + args: { onClick: fn() }, + // More on argTypes: https://storybook.js.org/docs/api/argtypes + argTypes: { + backgroundColor: { control: 'color' }, + }, + component: Renderable, + parameters: { + // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout + //layout: 'centered', + }, + // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs + tags: ['autodocs'], + title: 'Former/Controls Basic', +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args +export const BasicExample: Story = { + args: { + label: 'Test', + }, +};