Migrate to a monorepo structure (#2909)
This commit is contained in:
@ -0,0 +1,72 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { validateMetadataLabel } from '@/object-metadata/utils/validateMetadataLabel';
|
||||
import { H2Title } from '@/ui/display/typography/components/H2Title';
|
||||
import { IconPicker } from '@/ui/input/components/IconPicker';
|
||||
import { TextArea } from '@/ui/input/components/TextArea';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
|
||||
type SettingsObjectFieldFormSectionProps = {
|
||||
disabled?: boolean;
|
||||
disableNameEdition?: boolean;
|
||||
name?: string;
|
||||
description?: string;
|
||||
iconKey?: string;
|
||||
onChange?: (
|
||||
formValues: Partial<{
|
||||
icon: string;
|
||||
label: string;
|
||||
description: string;
|
||||
}>,
|
||||
) => void;
|
||||
};
|
||||
|
||||
const StyledInputsContainer = styled.div`
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
margin-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const SettingsObjectFieldFormSection = ({
|
||||
disabled,
|
||||
disableNameEdition,
|
||||
name = '',
|
||||
description = '',
|
||||
iconKey = 'IconUsers',
|
||||
onChange,
|
||||
}: SettingsObjectFieldFormSectionProps) => (
|
||||
<Section>
|
||||
<H2Title
|
||||
title="Name and description"
|
||||
description="The name and description of this field"
|
||||
/>
|
||||
<StyledInputsContainer>
|
||||
<IconPicker
|
||||
disabled={disabled}
|
||||
selectedIconKey={iconKey}
|
||||
onChange={(value) => onChange?.({ icon: value.iconKey })}
|
||||
variant="primary"
|
||||
/>
|
||||
<TextInput
|
||||
placeholder="Employees"
|
||||
value={name}
|
||||
onChange={(value) => {
|
||||
if (!value || validateMetadataLabel(value)) {
|
||||
onChange?.({ label: value });
|
||||
}
|
||||
}}
|
||||
disabled={disabled || disableNameEdition}
|
||||
fullWidth
|
||||
/>
|
||||
</StyledInputsContainer>
|
||||
<TextArea
|
||||
placeholder="Write a description"
|
||||
minRows={4}
|
||||
value={description}
|
||||
onChange={(value) => onChange?.({ description: value })}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Section>
|
||||
);
|
||||
@ -0,0 +1,159 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { parseFieldType } from '@/object-metadata/utils/parseFieldType';
|
||||
import { FieldDisplay } from '@/object-record/field/components/FieldDisplay';
|
||||
import { FieldContext } from '@/object-record/field/contexts/FieldContext';
|
||||
import { BooleanFieldInput } from '@/object-record/field/meta-types/input/components/BooleanFieldInput';
|
||||
import { RatingFieldInput } from '@/object-record/field/meta-types/input/components/RatingFieldInput';
|
||||
import { Tag } from '@/ui/display/tag/components/Tag';
|
||||
import { Field, FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
import { SettingsObjectFieldPreviewValueEffect } from '../components/SettingsObjectFieldPreviewValueEffect';
|
||||
import { useFieldPreview } from '../hooks/useFieldPreview';
|
||||
|
||||
import { SettingsObjectFieldSelectFormValues } from './SettingsObjectFieldSelectForm';
|
||||
|
||||
export type SettingsObjectFieldPreviewProps = {
|
||||
className?: string;
|
||||
fieldMetadata: Pick<Field, 'icon' | 'label' | 'type'> & { id?: string };
|
||||
objectMetadataId: string;
|
||||
relationObjectMetadataId?: string;
|
||||
selectOptions?: SettingsObjectFieldSelectFormValues;
|
||||
shrink?: boolean;
|
||||
};
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
background-color: ${({ theme }) => theme.background.secondary};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||
box-sizing: border-box;
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
max-width: 480px;
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledObjectSummary = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
justify-content: space-between;
|
||||
margin-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledObjectName = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const StyledFieldPreview = styled.div<{ shrink?: boolean }>`
|
||||
align-items: center;
|
||||
background-color: ${({ theme }) => theme.background.primary};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
height: ${({ theme }) => theme.spacing(8)};
|
||||
overflow: hidden;
|
||||
padding: 0
|
||||
${({ shrink, theme }) => (shrink ? theme.spacing(1) : theme.spacing(2))};
|
||||
white-space: nowrap;
|
||||
`;
|
||||
|
||||
const StyledFieldLabel = styled.div`
|
||||
align-items: center;
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
export const SettingsObjectFieldPreview = ({
|
||||
className,
|
||||
fieldMetadata,
|
||||
objectMetadataId,
|
||||
relationObjectMetadataId,
|
||||
selectOptions,
|
||||
shrink,
|
||||
}: SettingsObjectFieldPreviewProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const {
|
||||
entityId,
|
||||
FieldIcon,
|
||||
fieldName,
|
||||
ObjectIcon,
|
||||
objectMetadataItem,
|
||||
relationObjectMetadataItem,
|
||||
value,
|
||||
} = useFieldPreview({
|
||||
fieldMetadata,
|
||||
objectMetadataId,
|
||||
relationObjectMetadataId,
|
||||
selectOptions,
|
||||
});
|
||||
|
||||
return (
|
||||
<StyledContainer className={className}>
|
||||
<StyledObjectSummary>
|
||||
<StyledObjectName>
|
||||
{!!ObjectIcon && (
|
||||
<ObjectIcon
|
||||
size={theme.icon.size.sm}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
)}
|
||||
{objectMetadataItem?.labelPlural}
|
||||
</StyledObjectName>
|
||||
{objectMetadataItem?.isCustom ? (
|
||||
<Tag color="orange" text="Custom" />
|
||||
) : (
|
||||
<Tag color="blue" text="Standard" />
|
||||
)}
|
||||
</StyledObjectSummary>
|
||||
<SettingsObjectFieldPreviewValueEffect
|
||||
entityId={entityId}
|
||||
fieldName={fieldName}
|
||||
value={value}
|
||||
/>
|
||||
<StyledFieldPreview shrink={shrink}>
|
||||
<StyledFieldLabel>
|
||||
{!!FieldIcon && (
|
||||
<FieldIcon
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
)}
|
||||
{fieldMetadata.label}:
|
||||
</StyledFieldLabel>
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
entityId,
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition: {
|
||||
type: parseFieldType(fieldMetadata.type),
|
||||
iconName: 'FieldIcon',
|
||||
fieldMetadataId: fieldMetadata.id || '',
|
||||
label: fieldMetadata.label,
|
||||
metadata: {
|
||||
fieldName,
|
||||
relationObjectMetadataNameSingular:
|
||||
relationObjectMetadataItem?.nameSingular,
|
||||
},
|
||||
},
|
||||
hotkeyScope: 'field-preview',
|
||||
}}
|
||||
>
|
||||
{fieldMetadata.type === FieldMetadataType.Boolean ? (
|
||||
<BooleanFieldInput readonly />
|
||||
) : fieldMetadata.type === FieldMetadataType.Rating ? (
|
||||
<RatingFieldInput readonly />
|
||||
) : (
|
||||
<FieldDisplay />
|
||||
)}
|
||||
</FieldContext.Provider>
|
||||
</StyledFieldPreview>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,29 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { entityFieldsFamilySelector } from '@/object-record/field/states/selectors/entityFieldsFamilySelector';
|
||||
|
||||
type SettingsObjectFieldPreviewValueEffectProps = {
|
||||
entityId: string;
|
||||
fieldName: string;
|
||||
value: unknown;
|
||||
};
|
||||
|
||||
export const SettingsObjectFieldPreviewValueEffect = ({
|
||||
entityId,
|
||||
fieldName,
|
||||
value,
|
||||
}: SettingsObjectFieldPreviewValueEffectProps) => {
|
||||
const [, setFieldValue] = useRecoilState(
|
||||
entityFieldsFamilySelector({
|
||||
entityId,
|
||||
fieldName,
|
||||
}),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setFieldValue(value);
|
||||
}, [value, setFieldValue]);
|
||||
|
||||
return null;
|
||||
};
|
||||
@ -0,0 +1,131 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings';
|
||||
import { validateMetadataLabel } from '@/object-metadata/utils/validateMetadataLabel';
|
||||
import { IconPicker } from '@/ui/input/components/IconPicker';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { useLazyLoadIcons } from '@/ui/input/hooks/useLazyLoadIcons';
|
||||
import { Field } from '~/generated-metadata/graphql';
|
||||
|
||||
import { relationTypes } from '../constants/relationTypes';
|
||||
import { RelationType } from '../types/RelationType';
|
||||
|
||||
export type SettingsObjectFieldRelationFormValues = {
|
||||
field: Pick<Field, 'icon' | 'label'>;
|
||||
objectMetadataId: string;
|
||||
type: RelationType;
|
||||
};
|
||||
|
||||
type SettingsObjectFieldRelationFormProps = {
|
||||
disableFieldEdition?: boolean;
|
||||
disableRelationEdition?: boolean;
|
||||
onChange: (values: Partial<SettingsObjectFieldRelationFormValues>) => void;
|
||||
values: SettingsObjectFieldRelationFormValues;
|
||||
};
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
padding: ${({ theme }) => theme.spacing(4)};
|
||||
`;
|
||||
|
||||
const StyledSelectsContainer = styled.div`
|
||||
display: grid;
|
||||
gap: ${({ theme }) => theme.spacing(4)};
|
||||
grid-template-columns: 1fr 1fr;
|
||||
margin-bottom: ${({ theme }) => theme.spacing(4)};
|
||||
`;
|
||||
|
||||
const StyledInputsLabel = styled.span`
|
||||
color: ${({ theme }) => theme.font.color.light};
|
||||
display: block;
|
||||
font-size: ${({ theme }) => theme.font.size.xs};
|
||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||
margin-bottom: ${({ theme }) => theme.spacing(1)};
|
||||
text-transform: uppercase;
|
||||
`;
|
||||
|
||||
const StyledInputsContainer = styled.div`
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const SettingsObjectFieldRelationForm = ({
|
||||
disableFieldEdition,
|
||||
disableRelationEdition,
|
||||
onChange,
|
||||
values,
|
||||
}: SettingsObjectFieldRelationFormProps) => {
|
||||
const { icons } = useLazyLoadIcons();
|
||||
const { objectMetadataItems, findObjectMetadataItemById } =
|
||||
useObjectMetadataItemForSettings();
|
||||
|
||||
const selectedObjectMetadataItem =
|
||||
(values.objectMetadataId
|
||||
? findObjectMetadataItemById(values.objectMetadataId)
|
||||
: undefined) || objectMetadataItems[0];
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledSelectsContainer>
|
||||
<Select
|
||||
label="Relation type"
|
||||
dropdownScopeId="relation-type-select"
|
||||
disabled={disableRelationEdition}
|
||||
value={values.type}
|
||||
options={Object.entries(relationTypes).map(
|
||||
([value, { label, Icon }]) => ({
|
||||
label,
|
||||
value: value as RelationType,
|
||||
Icon,
|
||||
}),
|
||||
)}
|
||||
onChange={(value) => onChange({ type: value })}
|
||||
/>
|
||||
<Select
|
||||
label="Object destination"
|
||||
dropdownScopeId="object-destination-select"
|
||||
disabled={disableRelationEdition}
|
||||
value={values.objectMetadataId}
|
||||
options={objectMetadataItems.map((objectMetadataItem) => ({
|
||||
label: objectMetadataItem.labelPlural,
|
||||
value: objectMetadataItem.id,
|
||||
Icon: objectMetadataItem.icon
|
||||
? icons[objectMetadataItem.icon]
|
||||
: undefined,
|
||||
}))}
|
||||
onChange={(value) => onChange({ objectMetadataId: value })}
|
||||
/>
|
||||
</StyledSelectsContainer>
|
||||
<StyledInputsLabel>
|
||||
Field on {selectedObjectMetadataItem?.labelPlural}
|
||||
</StyledInputsLabel>
|
||||
<StyledInputsContainer>
|
||||
<IconPicker
|
||||
disabled={disableFieldEdition}
|
||||
dropdownScopeId="field-destination-icon-picker"
|
||||
selectedIconKey={values.field.icon || undefined}
|
||||
onChange={(value) =>
|
||||
onChange({
|
||||
field: { ...values.field, icon: value.iconKey },
|
||||
})
|
||||
}
|
||||
variant="primary"
|
||||
/>
|
||||
<TextInput
|
||||
disabled={disableFieldEdition}
|
||||
placeholder="Field name"
|
||||
value={values.field.label}
|
||||
onChange={(value) => {
|
||||
if (!value || validateMetadataLabel(value)) {
|
||||
onChange({
|
||||
field: { ...values.field, label: value },
|
||||
});
|
||||
}
|
||||
}}
|
||||
fullWidth
|
||||
/>
|
||||
</StyledInputsContainer>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,137 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { DropResult } from '@hello-pangea/dnd';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { IconPlus } from '@/ui/display/icon';
|
||||
import { Button } from '@/ui/input/button/components/Button';
|
||||
import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem';
|
||||
import { DraggableList } from '@/ui/layout/draggable-list/components/DraggableList';
|
||||
import { mainColorNames, ThemeColor } from '@/ui/theme/constants/colors';
|
||||
|
||||
import { SettingsObjectFieldSelectFormOption } from '../types/SettingsObjectFieldSelectFormOption';
|
||||
|
||||
import { SettingsObjectFieldSelectFormOptionRow } from './SettingsObjectFieldSelectFormOptionRow';
|
||||
|
||||
export type SettingsObjectFieldSelectFormValues =
|
||||
SettingsObjectFieldSelectFormOption[];
|
||||
|
||||
type SettingsObjectFieldSelectFormProps = {
|
||||
onChange: (values: SettingsObjectFieldSelectFormValues) => void;
|
||||
values: SettingsObjectFieldSelectFormValues;
|
||||
};
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
padding: ${({ theme }) => theme.spacing(4)};
|
||||
padding-bottom: ${({ theme }) => theme.spacing(3.5)};
|
||||
`;
|
||||
|
||||
const StyledLabel = styled.span`
|
||||
color: ${({ theme }) => theme.font.color.light};
|
||||
display: block;
|
||||
font-size: ${({ theme }) => theme.font.size.xs};
|
||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||
margin-bottom: 6px;
|
||||
margin-top: ${({ theme }) => theme.spacing(1)};
|
||||
text-transform: uppercase;
|
||||
`;
|
||||
|
||||
const StyledButton = styled(Button)`
|
||||
border-bottom: 0;
|
||||
border-left: 0;
|
||||
border-radius: 0;
|
||||
border-right: 0;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const getNextColor = (currentColor: ThemeColor) => {
|
||||
const currentColorIndex = mainColorNames.findIndex(
|
||||
(color) => color === currentColor,
|
||||
);
|
||||
const nextColorIndex = (currentColorIndex + 1) % mainColorNames.length;
|
||||
return mainColorNames[nextColorIndex];
|
||||
};
|
||||
|
||||
export const SettingsObjectFieldSelectForm = ({
|
||||
onChange,
|
||||
values,
|
||||
}: SettingsObjectFieldSelectFormProps) => {
|
||||
const handleDragEnd = (result: DropResult) => {
|
||||
if (!result.destination) return;
|
||||
|
||||
const nextOptions = [...values];
|
||||
const [movedOption] = nextOptions.splice(result.source.index, 1);
|
||||
|
||||
nextOptions.splice(result.destination.index, 0, movedOption);
|
||||
|
||||
onChange(nextOptions);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledContainer>
|
||||
<StyledLabel>Options</StyledLabel>
|
||||
<DraggableList
|
||||
onDragEnd={handleDragEnd}
|
||||
draggableItems={
|
||||
<>
|
||||
{values.map((option, index) => (
|
||||
<DraggableItem
|
||||
key={option.value}
|
||||
draggableId={option.value}
|
||||
index={index}
|
||||
isDragDisabled={values.length === 1}
|
||||
itemComponent={
|
||||
<SettingsObjectFieldSelectFormOptionRow
|
||||
key={option.value}
|
||||
isDefault={option.isDefault}
|
||||
onChange={(nextOption) => {
|
||||
const hasDefaultOptionChanged =
|
||||
!option.isDefault && nextOption.isDefault;
|
||||
const nextOptions = hasDefaultOptionChanged
|
||||
? values.map((value) => ({
|
||||
...value,
|
||||
isDefault: false,
|
||||
}))
|
||||
: [...values];
|
||||
|
||||
nextOptions.splice(index, 1, nextOption);
|
||||
|
||||
onChange(nextOptions);
|
||||
}}
|
||||
onRemove={
|
||||
values.length > 1
|
||||
? () => {
|
||||
const nextOptions = [...values];
|
||||
nextOptions.splice(index, 1);
|
||||
onChange(nextOptions);
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
option={option}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</StyledContainer>
|
||||
<StyledButton
|
||||
title="Add option"
|
||||
fullWidth
|
||||
Icon={IconPlus}
|
||||
onClick={() =>
|
||||
onChange([
|
||||
...values,
|
||||
{
|
||||
color: getNextColor(values[values.length - 1].color),
|
||||
label: `Option ${values.length + 1}`,
|
||||
value: v4(),
|
||||
},
|
||||
])
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,163 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { ColorSample } from '@/ui/display/color/components/ColorSample';
|
||||
import {
|
||||
IconCheck,
|
||||
IconDotsVertical,
|
||||
IconGripVertical,
|
||||
IconTrash,
|
||||
IconX,
|
||||
} from '@/ui/display/icon';
|
||||
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
|
||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||
import { MenuItemSelectColor } from '@/ui/navigation/menu-item/components/MenuItemSelectColor';
|
||||
import { mainColorNames } from '@/ui/theme/constants/colors';
|
||||
|
||||
import { SettingsObjectFieldSelectFormOption } from '../types/SettingsObjectFieldSelectFormOption';
|
||||
|
||||
type SettingsObjectFieldSelectFormOptionRowProps = {
|
||||
className?: string;
|
||||
isDefault?: boolean;
|
||||
onChange: (value: SettingsObjectFieldSelectFormOption) => void;
|
||||
onRemove?: () => void;
|
||||
option: SettingsObjectFieldSelectFormOption;
|
||||
};
|
||||
|
||||
const StyledRow = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: ${({ theme }) => theme.spacing(6)};
|
||||
padding: ${({ theme }) => theme.spacing(1.5)} 0;
|
||||
`;
|
||||
|
||||
const StyledColorSample = styled(ColorSample)`
|
||||
cursor: pointer;
|
||||
margin-left: 9px;
|
||||
margin-right: 14px;
|
||||
`;
|
||||
|
||||
const StyledOptionInput = styled(TextInput)`
|
||||
flex: 1 0 auto;
|
||||
margin-right: ${({ theme }) => theme.spacing(2)};
|
||||
|
||||
& input {
|
||||
height: ${({ theme }) => theme.spacing(2)};
|
||||
}
|
||||
`;
|
||||
|
||||
export const SettingsObjectFieldSelectFormOptionRow = ({
|
||||
className,
|
||||
isDefault,
|
||||
onChange,
|
||||
onRemove,
|
||||
option,
|
||||
}: SettingsObjectFieldSelectFormOptionRowProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const dropdownScopeIds = useMemo(() => {
|
||||
const baseScopeId = `select-field-option-row-${v4()}`;
|
||||
return { color: `${baseScopeId}-color`, actions: `${baseScopeId}-actions` };
|
||||
}, []);
|
||||
|
||||
const { closeDropdown: closeColorDropdown } = useDropdown({
|
||||
dropdownScopeId: dropdownScopeIds.color,
|
||||
});
|
||||
const { closeDropdown: closeActionsDropdown } = useDropdown({
|
||||
dropdownScopeId: dropdownScopeIds.actions,
|
||||
});
|
||||
|
||||
return (
|
||||
<StyledRow className={className}>
|
||||
<IconGripVertical
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
color={theme.font.color.extraLight}
|
||||
/>
|
||||
<DropdownScope dropdownScopeId={dropdownScopeIds.color}>
|
||||
<Dropdown
|
||||
dropdownPlacement="bottom-start"
|
||||
dropdownHotkeyScope={{
|
||||
scope: dropdownScopeIds.color,
|
||||
}}
|
||||
clickableComponent={<StyledColorSample colorName={option.color} />}
|
||||
dropdownComponents={
|
||||
<DropdownMenu>
|
||||
<DropdownMenuItemsContainer>
|
||||
{mainColorNames.map((colorName) => (
|
||||
<MenuItemSelectColor
|
||||
key={colorName}
|
||||
onClick={() => {
|
||||
onChange({ ...option, color: colorName });
|
||||
closeColorDropdown();
|
||||
}}
|
||||
color={colorName}
|
||||
selected={colorName === option.color}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownMenu>
|
||||
}
|
||||
/>
|
||||
</DropdownScope>
|
||||
<StyledOptionInput
|
||||
value={option.label}
|
||||
onChange={(label) => onChange({ ...option, label })}
|
||||
RightIcon={isDefault ? IconCheck : undefined}
|
||||
/>
|
||||
<DropdownScope dropdownScopeId={dropdownScopeIds.actions}>
|
||||
<Dropdown
|
||||
dropdownPlacement="right-start"
|
||||
dropdownHotkeyScope={{
|
||||
scope: dropdownScopeIds.actions,
|
||||
}}
|
||||
clickableComponent={<LightIconButton Icon={IconDotsVertical} />}
|
||||
dropdownComponents={
|
||||
<DropdownMenu>
|
||||
<DropdownMenuItemsContainer>
|
||||
{isDefault ? (
|
||||
<MenuItem
|
||||
LeftIcon={IconX}
|
||||
text="Remove as default"
|
||||
onClick={() => {
|
||||
onChange({ ...option, isDefault: false });
|
||||
closeActionsDropdown();
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<MenuItem
|
||||
LeftIcon={IconCheck}
|
||||
text="Set as default"
|
||||
onClick={() => {
|
||||
onChange({ ...option, isDefault: true });
|
||||
closeActionsDropdown();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{!!onRemove && (
|
||||
<MenuItem
|
||||
accent="danger"
|
||||
LeftIcon={IconTrash}
|
||||
text="Remove option"
|
||||
onClick={() => {
|
||||
onRemove();
|
||||
closeActionsDropdown();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownMenu>
|
||||
}
|
||||
/>
|
||||
</DropdownScope>
|
||||
</StyledRow>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,57 @@
|
||||
import { ReactNode } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { Card } from '@/ui/layout/card/components/Card';
|
||||
|
||||
type SettingsObjectFieldTypeCardProps = {
|
||||
className?: string;
|
||||
preview: ReactNode;
|
||||
form?: ReactNode;
|
||||
};
|
||||
|
||||
const StyledPreviewContainer = styled(Card)`
|
||||
background-color: ${({ theme }) => theme.background.transparent.lighter};
|
||||
padding: ${({ theme }) => theme.spacing(4)};
|
||||
|
||||
&:not(:last-child) {
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledTitle = styled.h3`
|
||||
color: ${({ theme }) => theme.font.color.extraLight};
|
||||
font-size: ${({ theme }) => theme.font.size.sm};
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
margin: 0;
|
||||
margin-bottom: ${({ theme }) => theme.spacing(4)};
|
||||
`;
|
||||
|
||||
const StyledPreviewContent = styled.div`
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
`;
|
||||
|
||||
const StyledFormContainer = styled(Card)`
|
||||
border-top: 0;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
`;
|
||||
|
||||
export const SettingsObjectFieldTypeCard = ({
|
||||
className,
|
||||
preview,
|
||||
form,
|
||||
}: SettingsObjectFieldTypeCardProps) => {
|
||||
return (
|
||||
<div className={className}>
|
||||
<StyledPreviewContainer>
|
||||
<StyledTitle>Preview</StyledTitle>
|
||||
<StyledPreviewContent>{preview}</StyledPreviewContent>
|
||||
</StyledPreviewContainer>
|
||||
{!!form && <StyledFormContainer>{form}</StyledFormContainer>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,165 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { H2Title } from '@/ui/display/typography/components/H2Title';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
import { Field, FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
import { relationTypes } from '../constants/relationTypes';
|
||||
import { settingsFieldMetadataTypes } from '../constants/settingsFieldMetadataTypes';
|
||||
|
||||
import {
|
||||
SettingsObjectFieldPreview,
|
||||
SettingsObjectFieldPreviewProps,
|
||||
} from './SettingsObjectFieldPreview';
|
||||
import {
|
||||
SettingsObjectFieldRelationForm,
|
||||
SettingsObjectFieldRelationFormValues,
|
||||
} from './SettingsObjectFieldRelationForm';
|
||||
import {
|
||||
SettingsObjectFieldSelectForm,
|
||||
SettingsObjectFieldSelectFormValues,
|
||||
} from './SettingsObjectFieldSelectForm';
|
||||
import { SettingsObjectFieldTypeCard } from './SettingsObjectFieldTypeCard';
|
||||
|
||||
export type SettingsObjectFieldTypeSelectSectionFormValues = {
|
||||
type: FieldMetadataType;
|
||||
relation: SettingsObjectFieldRelationFormValues;
|
||||
select: SettingsObjectFieldSelectFormValues;
|
||||
};
|
||||
|
||||
type SettingsObjectFieldTypeSelectSectionProps = {
|
||||
excludedFieldTypes?: FieldMetadataType[];
|
||||
fieldMetadata: Pick<Field, 'icon' | 'label'> & { id?: string };
|
||||
onChange: (
|
||||
values: Partial<SettingsObjectFieldTypeSelectSectionFormValues>,
|
||||
) => void;
|
||||
relationFieldMetadata?: Pick<Field, 'id' | 'isCustom'>;
|
||||
values: SettingsObjectFieldTypeSelectSectionFormValues;
|
||||
} & Pick<SettingsObjectFieldPreviewProps, 'objectMetadataId'>;
|
||||
|
||||
const StyledSettingsObjectFieldTypeCard = styled(SettingsObjectFieldTypeCard)`
|
||||
margin-top: ${({ theme }) => theme.spacing(4)};
|
||||
`;
|
||||
|
||||
const StyledSettingsObjectFieldPreview = styled(SettingsObjectFieldPreview)`
|
||||
display: grid;
|
||||
flex: 1 1 100%;
|
||||
`;
|
||||
|
||||
const StyledRelationImage = styled.img<{ flip?: boolean }>`
|
||||
transform: ${({ flip }) => (flip ? 'scaleX(-1)' : 'none')};
|
||||
width: 54px;
|
||||
`;
|
||||
|
||||
export const SettingsObjectFieldTypeSelectSection = ({
|
||||
excludedFieldTypes,
|
||||
fieldMetadata,
|
||||
objectMetadataId,
|
||||
onChange,
|
||||
relationFieldMetadata,
|
||||
values,
|
||||
}: SettingsObjectFieldTypeSelectSectionProps) => {
|
||||
const relationFormConfig = values.relation;
|
||||
const selectFormConfig = values.select;
|
||||
|
||||
const fieldTypeOptions = Object.entries(settingsFieldMetadataTypes)
|
||||
.filter(([key]) => !excludedFieldTypes?.includes(key as FieldMetadataType))
|
||||
.map(([key, dataTypeConfig]) => ({
|
||||
value: key as FieldMetadataType,
|
||||
...dataTypeConfig,
|
||||
}));
|
||||
|
||||
return (
|
||||
<Section>
|
||||
<H2Title
|
||||
title="Type and values"
|
||||
description="The field's type and values."
|
||||
/>
|
||||
<Select
|
||||
disabled={!!fieldMetadata?.id}
|
||||
dropdownScopeId="object-field-type-select"
|
||||
value={values?.type}
|
||||
onChange={(value) => onChange({ type: value })}
|
||||
options={fieldTypeOptions}
|
||||
/>
|
||||
{!!values?.type &&
|
||||
[
|
||||
FieldMetadataType.Boolean,
|
||||
FieldMetadataType.Currency,
|
||||
FieldMetadataType.DateTime,
|
||||
FieldMetadataType.Select,
|
||||
FieldMetadataType.Link,
|
||||
FieldMetadataType.Number,
|
||||
FieldMetadataType.Rating,
|
||||
FieldMetadataType.Relation,
|
||||
FieldMetadataType.Text,
|
||||
].includes(values.type) && (
|
||||
<StyledSettingsObjectFieldTypeCard
|
||||
preview={
|
||||
<>
|
||||
<StyledSettingsObjectFieldPreview
|
||||
fieldMetadata={{
|
||||
...fieldMetadata,
|
||||
type: values.type,
|
||||
}}
|
||||
shrink={values.type === FieldMetadataType.Relation}
|
||||
objectMetadataId={objectMetadataId}
|
||||
relationObjectMetadataId={
|
||||
relationFormConfig?.objectMetadataId
|
||||
}
|
||||
selectOptions={selectFormConfig}
|
||||
/>
|
||||
{values.type === FieldMetadataType.Relation &&
|
||||
!!relationFormConfig?.type &&
|
||||
!!relationFormConfig.objectMetadataId && (
|
||||
<>
|
||||
<StyledRelationImage
|
||||
src={relationTypes[relationFormConfig.type].imageSrc}
|
||||
flip={
|
||||
relationTypes[relationFormConfig.type].isImageFlipped
|
||||
}
|
||||
alt={relationTypes[relationFormConfig.type].label}
|
||||
/>
|
||||
<StyledSettingsObjectFieldPreview
|
||||
fieldMetadata={{
|
||||
...relationFormConfig.field,
|
||||
label:
|
||||
relationFormConfig.field?.label || 'Field name',
|
||||
type: FieldMetadataType.Relation,
|
||||
id: relationFieldMetadata?.id,
|
||||
}}
|
||||
shrink
|
||||
objectMetadataId={relationFormConfig.objectMetadataId}
|
||||
relationObjectMetadataId={objectMetadataId}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
form={
|
||||
values.type === FieldMetadataType.Relation ? (
|
||||
<SettingsObjectFieldRelationForm
|
||||
disableFieldEdition={
|
||||
relationFieldMetadata && !relationFieldMetadata.isCustom
|
||||
}
|
||||
disableRelationEdition={!!relationFieldMetadata}
|
||||
values={relationFormConfig}
|
||||
onChange={(nextValues) =>
|
||||
onChange({
|
||||
relation: { ...relationFormConfig, ...nextValues },
|
||||
})
|
||||
}
|
||||
/>
|
||||
) : values.type === FieldMetadataType.Select ? (
|
||||
<SettingsObjectFieldSelectForm
|
||||
values={selectFormConfig}
|
||||
onChange={(nextValues) => onChange({ select: nextValues })}
|
||||
/>
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,76 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { validateMetadataLabel } from '@/object-metadata/utils/validateMetadataLabel';
|
||||
import { H2Title } from '@/ui/display/typography/components/H2Title';
|
||||
import { TextArea } from '@/ui/input/components/TextArea';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { Section } from '@/ui/layout/section/components/Section';
|
||||
|
||||
type SettingsObjectFormSectionProps = {
|
||||
disabled?: boolean;
|
||||
singularName?: string;
|
||||
pluralName?: string;
|
||||
description?: string;
|
||||
onChange?: (
|
||||
formValues: Partial<{
|
||||
labelSingular: string;
|
||||
labelPlural: string;
|
||||
description: string;
|
||||
}>,
|
||||
) => void;
|
||||
};
|
||||
|
||||
const StyledInputsContainer = styled.div`
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
margin-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const SettingsObjectFormSection = ({
|
||||
disabled,
|
||||
singularName = '',
|
||||
pluralName = '',
|
||||
description = '',
|
||||
onChange,
|
||||
}: SettingsObjectFormSectionProps) => (
|
||||
<Section>
|
||||
<H2Title
|
||||
title="Name and description"
|
||||
description="Name in both singular (e.g., 'Invoice') and plural (e.g., 'Invoices') forms."
|
||||
/>
|
||||
<StyledInputsContainer>
|
||||
<TextInput
|
||||
label="Singular"
|
||||
placeholder="Investor"
|
||||
value={singularName}
|
||||
onChange={(value) => {
|
||||
if (!value || validateMetadataLabel(value)) {
|
||||
onChange?.({ labelSingular: value });
|
||||
}
|
||||
}}
|
||||
disabled={disabled}
|
||||
fullWidth
|
||||
/>
|
||||
<TextInput
|
||||
label="Plural"
|
||||
placeholder="Investors"
|
||||
value={pluralName}
|
||||
onChange={(value) => {
|
||||
if (!value || validateMetadataLabel(value)) {
|
||||
onChange?.({ labelPlural: value });
|
||||
}
|
||||
}}
|
||||
disabled={disabled}
|
||||
fullWidth
|
||||
/>
|
||||
</StyledInputsContainer>
|
||||
<TextArea
|
||||
placeholder="Write a description"
|
||||
minRows={4}
|
||||
value={description}
|
||||
onChange={(value) => onChange?.({ description: value })}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Section>
|
||||
);
|
||||
@ -0,0 +1,24 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
|
||||
import { SettingsObjectFieldFormSection } from '../SettingsObjectFieldFormSection';
|
||||
|
||||
const meta: Meta<typeof SettingsObjectFieldFormSection> = {
|
||||
title: 'Modules/Settings/DataModel/SettingsObjectFieldFormSection',
|
||||
component: SettingsObjectFieldFormSection,
|
||||
decorators: [ComponentDecorator],
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof SettingsObjectFieldFormSection>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const WithDefaultValues: Story = {
|
||||
args: {
|
||||
iconKey: 'IconLink',
|
||||
name: 'URL',
|
||||
description: 'Lorem ipsum',
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,127 @@
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { ObjectMetadataItemsProvider } from '@/object-metadata/components/ObjectMetadataItemsProvider';
|
||||
import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
|
||||
import { Field, FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import {
|
||||
mockedCompaniesMetadata,
|
||||
mockedPeopleMetadata,
|
||||
mockedWorkspacesMetadata,
|
||||
} from '~/testing/mock-data/metadata';
|
||||
|
||||
import { SettingsObjectFieldPreview } from '../SettingsObjectFieldPreview';
|
||||
|
||||
const meta: Meta<typeof SettingsObjectFieldPreview> = {
|
||||
title: 'Modules/Settings/DataModel/SettingsObjectFieldPreview',
|
||||
component: SettingsObjectFieldPreview,
|
||||
decorators: [
|
||||
ComponentDecorator,
|
||||
(Story) => (
|
||||
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
|
||||
<ObjectMetadataItemsProvider>
|
||||
<Story />
|
||||
</ObjectMetadataItemsProvider>
|
||||
</SnackBarProviderScope>
|
||||
),
|
||||
],
|
||||
args: {
|
||||
fieldMetadata: mockedCompaniesMetadata.node.fields.edges.find(
|
||||
({ node }) => node.type === FieldMetadataType.Text,
|
||||
)?.node,
|
||||
objectMetadataId: mockedCompaniesMetadata.node.id,
|
||||
},
|
||||
parameters: {
|
||||
msw: graphqlMocks,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof SettingsObjectFieldPreview>;
|
||||
|
||||
export const Text: Story = {};
|
||||
|
||||
export const Boolean: Story = {
|
||||
args: {
|
||||
fieldMetadata: mockedCompaniesMetadata.node.fields.edges.find(
|
||||
({ node }) => node.type === FieldMetadataType.Boolean,
|
||||
)?.node as Field,
|
||||
},
|
||||
};
|
||||
|
||||
export const Currency: Story = {
|
||||
args: {
|
||||
fieldMetadata: mockedCompaniesMetadata.node.fields.edges.find(
|
||||
({ node }) => node.type === FieldMetadataType.Currency,
|
||||
)?.node as Field,
|
||||
},
|
||||
};
|
||||
|
||||
export const Date: Story = {
|
||||
args: {
|
||||
fieldMetadata: mockedCompaniesMetadata.node.fields.edges.find(
|
||||
({ node }) => node.type === FieldMetadataType.DateTime,
|
||||
)?.node as Field,
|
||||
},
|
||||
};
|
||||
|
||||
export const Link: Story = {
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<MemoryRouter>
|
||||
<Story />
|
||||
</MemoryRouter>
|
||||
),
|
||||
],
|
||||
args: {
|
||||
fieldMetadata: mockedCompaniesMetadata.node.fields.edges.find(
|
||||
({ node }) => node.type === FieldMetadataType.Link,
|
||||
)?.node as Field,
|
||||
},
|
||||
};
|
||||
|
||||
export const Number: Story = {
|
||||
args: {
|
||||
fieldMetadata: mockedCompaniesMetadata.node.fields.edges.find(
|
||||
({ node }) => node.type === FieldMetadataType.Number,
|
||||
)?.node as Field,
|
||||
},
|
||||
};
|
||||
|
||||
export const Rating: Story = {
|
||||
args: {
|
||||
fieldMetadata: {
|
||||
icon: 'IconHandClick',
|
||||
label: 'Engagement',
|
||||
type: FieldMetadataType.Rating,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const Relation: Story = {
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<MemoryRouter>
|
||||
<Story />
|
||||
</MemoryRouter>
|
||||
),
|
||||
],
|
||||
args: {
|
||||
fieldMetadata: mockedPeopleMetadata.node.fields.edges.find(
|
||||
({ node }) => node.type === FieldMetadataType.Relation,
|
||||
)?.node as Field,
|
||||
objectMetadataId: mockedPeopleMetadata.node.id,
|
||||
relationObjectMetadataId: mockedCompaniesMetadata.node.id,
|
||||
},
|
||||
};
|
||||
|
||||
export const CustomObject: Story = {
|
||||
args: {
|
||||
fieldMetadata: mockedWorkspacesMetadata.node.fields.edges.find(
|
||||
({ node }) => node.type === FieldMetadataType.Text,
|
||||
)?.node as Field,
|
||||
objectMetadataId: mockedWorkspacesMetadata.node.id,
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,139 @@
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { userEvent, within } from '@storybook/test';
|
||||
|
||||
import { ObjectMetadataItemsProvider } from '@/object-metadata/components/ObjectMetadataItemsProvider';
|
||||
import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
RelationMetadataType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import {
|
||||
mockedCompaniesMetadata,
|
||||
mockedPeopleMetadata,
|
||||
} from '~/testing/mock-data/metadata';
|
||||
|
||||
import { fieldMetadataFormDefaultValues } from '../../hooks/useFieldMetadataForm';
|
||||
import {
|
||||
SettingsObjectFieldTypeSelectSection,
|
||||
SettingsObjectFieldTypeSelectSectionFormValues,
|
||||
} from '../SettingsObjectFieldTypeSelectSection';
|
||||
|
||||
const fieldMetadata = mockedCompaniesMetadata.node.fields.edges.find(
|
||||
({ node }) => node.type === FieldMetadataType.Text,
|
||||
)!.node;
|
||||
const { id: _id, ...fieldMetadataWithoutId } = fieldMetadata;
|
||||
|
||||
const meta: Meta<typeof SettingsObjectFieldTypeSelectSection> = {
|
||||
title: 'Modules/Settings/DataModel/SettingsObjectFieldTypeSelectSection',
|
||||
component: SettingsObjectFieldTypeSelectSection,
|
||||
decorators: [
|
||||
ComponentDecorator,
|
||||
(Story) => (
|
||||
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
|
||||
<ObjectMetadataItemsProvider>
|
||||
<Story />
|
||||
</ObjectMetadataItemsProvider>
|
||||
</SnackBarProviderScope>
|
||||
),
|
||||
],
|
||||
args: {
|
||||
fieldMetadata: fieldMetadataWithoutId,
|
||||
objectMetadataId: mockedCompaniesMetadata.node.id,
|
||||
values: fieldMetadataFormDefaultValues,
|
||||
},
|
||||
parameters: {
|
||||
container: { width: 512 },
|
||||
msw: graphqlMocks,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof SettingsObjectFieldTypeSelectSection>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
fieldMetadata,
|
||||
},
|
||||
};
|
||||
|
||||
export const WithOpenSelect: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
const input = await canvas.findByText('Unique ID');
|
||||
await userEvent.click(input);
|
||||
|
||||
const selectLabel = canvas.getByText('Number');
|
||||
|
||||
await userEvent.click(selectLabel);
|
||||
},
|
||||
};
|
||||
|
||||
const relationFieldMetadata = mockedPeopleMetadata.node.fields.edges.find(
|
||||
({ node }) => node.type === FieldMetadataType.Relation,
|
||||
)!.node;
|
||||
|
||||
export const WithRelationForm: Story = {
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<MemoryRouter>
|
||||
<Story />
|
||||
</MemoryRouter>
|
||||
),
|
||||
],
|
||||
args: {
|
||||
fieldMetadata: mockedCompaniesMetadata.node.fields.edges.find(
|
||||
({ node }) => node.type === FieldMetadataType.Relation,
|
||||
)?.node,
|
||||
relationFieldMetadata,
|
||||
values: {
|
||||
...fieldMetadataFormDefaultValues,
|
||||
type: FieldMetadataType.Relation,
|
||||
relation: {
|
||||
field: relationFieldMetadata,
|
||||
objectMetadataId: mockedPeopleMetadata.node.id,
|
||||
type: RelationMetadataType.OneToMany,
|
||||
},
|
||||
} as unknown as SettingsObjectFieldTypeSelectSectionFormValues,
|
||||
},
|
||||
};
|
||||
|
||||
export const WithSelectForm: Story = {
|
||||
args: {
|
||||
fieldMetadata: { label: 'Industry', icon: 'IconBuildingFactory2' },
|
||||
values: {
|
||||
...fieldMetadataFormDefaultValues,
|
||||
type: FieldMetadataType.Select,
|
||||
select: [
|
||||
{
|
||||
color: 'pink',
|
||||
isDefault: true,
|
||||
label: '💊 Health',
|
||||
value: 'HEALTH',
|
||||
},
|
||||
{
|
||||
color: 'purple',
|
||||
label: '🏭 Industry',
|
||||
value: 'INDUSTRY',
|
||||
},
|
||||
{ color: 'sky', label: '🤖 SaaS', value: 'SAAS' },
|
||||
{
|
||||
color: 'turquoise',
|
||||
label: '🌿 Green tech',
|
||||
value: 'GREEN_TECH',
|
||||
},
|
||||
{
|
||||
color: 'yellow',
|
||||
label: '🚲 Mobility',
|
||||
value: 'MOBILITY',
|
||||
},
|
||||
{ color: 'green', label: '🌏 NGO', value: 'NGO' },
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,24 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||
|
||||
import { SettingsObjectFormSection } from '../SettingsObjectFormSection';
|
||||
|
||||
const meta: Meta<typeof SettingsObjectFormSection> = {
|
||||
title: 'Modules/Settings/DataModel/SettingsObjectFormSection',
|
||||
component: SettingsObjectFormSection,
|
||||
decorators: [ComponentDecorator],
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof SettingsObjectFormSection>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const WithDefaultValues: Story = {
|
||||
args: {
|
||||
singularName: 'Company',
|
||||
pluralName: 'Companies',
|
||||
description: 'Lorem ipsum',
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user