feat(ui): ux improvements & redesign

This is a squash merge of a bajillion messy small commits created while iterating on the UI component library and redesign.
This commit is contained in:
psychedelicious
2023-12-29 00:03:21 +11:00
committed by Kent Keirsey
parent a47d91f0e7
commit f0b102d830
889 changed files with 16645 additions and 15595 deletions

View File

@@ -0,0 +1,95 @@
import type { Meta, StoryObj } from '@storybook/react';
import { InvNumberInput } from 'common/components/InvNumberInput/InvNumberInput';
import { InvSelect } from 'common/components/InvSelect/InvSelect';
import type { InvSelectOption } from 'common/components/InvSelect/types';
import { InvSlider } from 'common/components/InvSlider/InvSlider';
import { useState } from 'react';
import { InvControl } from './InvControl';
import type { InvControlProps } from './types';
const meta: Meta<typeof InvControl> = {
title: 'Primitives/InvControl',
tags: ['autodocs'],
component: InvControl,
args: {
label: 'My Control',
isDisabled: false,
isInvalid: false,
w: 96,
},
};
export default meta;
type Story = StoryObj<typeof InvControl>;
const InvControlWithSliderComponent = (props: InvControlProps) => {
const [value, setValue] = useState(0);
return (
<InvControl {...props}>
<InvSlider value={value} min={0} max={10} step={1} onChange={setValue} />
</InvControl>
);
};
const InvControlWithSliderAndHelperTextComponent = (props: InvControlProps) => {
const [value, setValue] = useState(0);
return (
<InvControl {...props} helperText="This is some helpful text">
<InvSlider value={value} min={0} max={10} step={1} onChange={setValue} />
</InvControl>
);
};
const InvControlWithNumberInputComponent = (props: InvControlProps) => {
const [value, setValue] = useState(0);
return (
<InvControl {...props}>
<InvNumberInput
value={value}
min={0}
max={10}
step={1}
onChange={setValue}
/>
</InvControl>
);
};
const options: InvSelectOption[] = [
{
value: 'chocolate',
label: 'Chocolate',
},
{
value: 'strawberry',
label: 'Strawberry',
},
{
value: 'vanilla',
label: 'Vanilla',
},
];
const InvControlWithSelectComponent = (props: InvControlProps) => {
return (
<InvControl {...props}>
<InvSelect defaultValue={options[0]} options={options} />
</InvControl>
);
};
export const InvControlWithSlider: Story = {
render: InvControlWithSliderComponent,
};
export const InvControlWithSliderAndHelperText: Story = {
render: InvControlWithSliderAndHelperTextComponent,
};
export const InvControlWithNumberInput: Story = {
render: InvControlWithNumberInputComponent,
};
export const InvControlWithSelect: Story = {
render: InvControlWithSelectComponent,
};

View File

@@ -0,0 +1,75 @@
import {
Flex,
FormControl as ChakraFormControl,
FormHelperText as ChakraFormHelperText,
forwardRef,
} from '@chakra-ui/react';
import { InvControlGroupContext } from 'common/components/InvControl/InvControlGroup';
import { useContext } from 'react';
import { InvLabel } from './InvLabel';
import type { InvControlProps } from './types';
export const InvControl = forwardRef<InvControlProps, typeof ChakraFormControl>(
(props: InvControlProps, ref) => {
const {
children,
helperText,
feature,
orientation,
renderInfoPopoverInPortal = true,
isDisabled,
labelProps,
label,
...formControlProps
} = props;
const ctx = useContext(InvControlGroupContext);
if (helperText) {
return (
<ChakraFormControl
ref={ref}
variant="withHelperText"
orientation={orientation ?? ctx.orientation}
isDisabled={isDisabled ?? ctx.isDisabled}
{...formControlProps}
>
<Flex>
{label && (
<InvLabel
feature={feature}
renderInfoPopoverInPortal={renderInfoPopoverInPortal}
{...labelProps}
>
{label}
</InvLabel>
)}
{children}
</Flex>
<ChakraFormHelperText>{helperText}</ChakraFormHelperText>
</ChakraFormControl>
);
}
return (
<ChakraFormControl
ref={ref}
isDisabled={isDisabled ?? ctx.isDisabled}
orientation={orientation ?? ctx.orientation}
{...formControlProps}
>
{label && (
<InvLabel
feature={feature}
renderInfoPopoverInPortal={renderInfoPopoverInPortal}
{...labelProps}
>
{label}
</InvLabel>
)}
{children}
</ChakraFormControl>
);
}
);

View File

@@ -0,0 +1,22 @@
import type { FormLabelProps } from '@chakra-ui/react';
import type { PropsWithChildren } from 'react';
import { createContext } from 'react';
export type InvControlGroupProps = {
labelProps?: FormLabelProps;
isDisabled?: boolean;
orientation?: 'horizontal' | 'vertical';
};
export const InvControlGroupContext = createContext<InvControlGroupProps>({});
export const InvControlGroup = ({
children,
...context
}: PropsWithChildren<InvControlGroupProps>) => {
return (
<InvControlGroupContext.Provider value={context}>
{children}
</InvControlGroupContext.Provider>
);
};

View File

@@ -0,0 +1,43 @@
import { Flex, FormLabel, forwardRef } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
import { InvControlGroupContext } from 'common/components/InvControl/InvControlGroup';
import { useContext } from 'react';
import type { InvLabelProps } from './types';
const selector = createSelector(
stateSelector,
({ system }) => system.shouldEnableInformationalPopovers
);
export const InvLabel = forwardRef<InvLabelProps, typeof FormLabel>(
(
{ feature, renderInfoPopoverInPortal, children, ...rest }: InvLabelProps,
ref
) => {
const shouldEnableInformationalPopovers = useAppSelector(selector);
const ctx = useContext(InvControlGroupContext);
if (feature && shouldEnableInformationalPopovers) {
return (
<IAIInformationalPopover
feature={feature}
inPortal={renderInfoPopoverInPortal}
>
<Flex as="span">
<FormLabel ref={ref} {...ctx.labelProps} {...rest}>
{children}
</FormLabel>
</Flex>
</IAIInformationalPopover>
);
}
return (
<FormLabel ref={ref} {...ctx.labelProps} {...rest}>
{children}
</FormLabel>
);
}
);

View File

@@ -0,0 +1,76 @@
import { formAnatomy as parts } from '@chakra-ui/anatomy';
import {
createMultiStyleConfigHelpers,
defineStyle,
defineStyleConfig,
} from '@chakra-ui/styled-system';
const { definePartsStyle, defineMultiStyleConfig } =
createMultiStyleConfigHelpers(parts.keys);
const formBaseStyle = definePartsStyle((props) => {
return {
container: {
display: 'flex',
flexDirection: props.orientation === 'vertical' ? 'column' : 'row',
alignItems: props.orientation === 'vertical' ? 'flex-start' : 'center',
gap: 4,
// h: props.orientation === 'vertical' ? 'unset' : 8,
},
};
});
const withHelperText = definePartsStyle(() => ({
container: {
flexDirection: 'column',
gap: 0,
h: 'unset',
'> div': {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
gap: 4,
h: 8,
w: 'full',
},
},
helperText: {
w: 'full',
fontSize: 'sm',
color: 'base.400',
m: 0,
},
}));
export const formTheme = defineMultiStyleConfig({
baseStyle: formBaseStyle,
variants: {
withHelperText,
},
});
const formLabelBaseStyle = defineStyle(() => {
return {
fontSize: 'sm',
marginEnd: 0,
mb: 0,
flexShrink: 0,
flexGrow: 0,
fontWeight: 'semibold',
transitionProperty: 'common',
transitionDuration: 'normal',
whiteSpace: 'nowrap',
userSelect: 'none',
_disabled: {
opacity: 0.4,
},
color: 'base.300',
_invalid: {
color: 'error.300',
},
};
});
export const formLabelTheme = defineStyleConfig({
baseStyle: formLabelBaseStyle,
});

View File

@@ -0,0 +1,21 @@
import type {
FormControlProps as ChakraFormControlProps,
FormLabelProps as ChakraFormLabelProps,
} from '@chakra-ui/react';
import type { Feature } from 'common/components/IAIInformationalPopover/constants';
export type InvControlProps = ChakraFormControlProps & {
label?: string;
helperText?: string;
feature?: Feature;
renderInfoPopoverInPortal?: boolean;
labelProps?: Omit<
InvLabelProps,
'children' | 'feature' | 'renderInfoPopoverInPortal'
>;
};
export type InvLabelProps = ChakraFormLabelProps & {
feature?: Feature;
renderInfoPopoverInPortal?: boolean;
};