Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(form-v2): set opacity for fields hidden by logic in builder #4482

Merged
merged 6 commits into from
Aug 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions frontend/src/features/admin-form/AdminFormCreatePage.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
import {
createFormBuilderMocks,
MOCK_FORM_FIELDS_WITH_MYINFO,
MOCK_FORM_LOGICS,
} from '~/mocks/msw/handlers/admin-form'
import { getFreeSmsQuota } from '~/mocks/msw/handlers/admin-form/twilio'
import { getUser, MOCK_USER } from '~/mocks/msw/handlers/user'
Expand Down Expand Up @@ -80,6 +81,7 @@ DesktopAllFields.parameters = {
responseMode: FormResponseMode.Email,
}),
}

export const DesktopLoading = Template.bind({})
DesktopLoading.parameters = {
msw: buildMswRoutes({}, 'infinite'),
Expand Down Expand Up @@ -110,3 +112,13 @@ MobileLoading.parameters = {
...getMobileViewParameters(),
msw: buildMswRoutes({}, 'infinite'),
}

export const AllFieldsFieldsHiddenByLogic = Template.bind({})
AllFieldsFieldsHiddenByLogic.parameters = {
msw: buildMswRoutes({
form_fields: MOCK_FORM_FIELDS_WITH_MYINFO,
form_logics: MOCK_FORM_LOGICS,
authType: FormAuthType.MyInfo,
responseMode: FormResponseMode.Email,
}),
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@ import { memo, useMemo } from 'react'
import { AdminFormDto } from '~shared/types/form'

import { augmentWithQuestionNo } from '~features/form/utils'
import { FieldIdSet } from '~features/logic/types'

import FieldRow from './FieldRow'

interface BuilderFieldsProps {
fields: AdminFormDto['form_fields']
visibleFieldIds: FieldIdSet
isDraggingOver: boolean
}

export const BuilderFields = memo(
({ fields, isDraggingOver }: BuilderFieldsProps) => {
({ fields, visibleFieldIds, isDraggingOver }: BuilderFieldsProps) => {
const fieldsWithQuestionNos = useMemo(
() => augmentWithQuestionNo(fields),
[fields],
Expand All @@ -25,6 +27,7 @@ export const BuilderFields = memo(
index={i}
key={f._id}
field={f}
isHiddenByLogic={!visibleFieldIds.has(f._id)}
isDraggingOver={isDraggingOver}
/>
))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { BasicField, FormFieldDto } from '~shared/types/field'

import { useIsMobile } from '~hooks/useIsMobile'
import IconButton from '~components/IconButton'
import Tooltip from '~components/Tooltip'
import {
AttachmentField,
CheckboxField,
Expand Down Expand Up @@ -68,12 +69,14 @@ import { SectionFieldRow } from './SectionFieldRow'
export interface FieldRowContainerProps {
field: FormFieldDto
index: number
isHiddenByLogic: boolean
isDraggingOver: boolean
}

export const FieldRowContainer = ({
field,
index,
isHiddenByLogic,
isDraggingOver,
}: FieldRowContainerProps): JSX.Element => {
const isMobile = useIsMobile()
Expand Down Expand Up @@ -210,116 +213,123 @@ export const FieldRowContainer = ({
{...provided.draggableProps}
ref={provided.innerRef}
>
<Flex
// Offset for focus boxShadow
my="2px"
// Focusable
tabIndex={0}
role="button"
cursor={isActive ? 'initial' : 'pointer'}
bg="white"
transition="background 0.2s ease"
_hover={{ bg: isDraggingOver ? 'white' : 'secondary.100' }}
borderRadius="4px"
outline="none"
{...(isActive ? { 'data-active': true } : {})}
_focusWithin={{
boxShadow: snapshot.isDragging
? 'md'
: '0 0 0 2px var(--chakra-colors-primary-500) !important',
}}
_active={{
bg: 'secondary.100',
boxShadow: snapshot.isDragging
? 'md'
: '0 0 0 2px var(--chakra-colors-primary-500)',
}}
flexDir="column"
align="center"
onClick={handleFieldClick}
onKeyDown={handleKeydown}
ref={ref}
<Tooltip
hidden={!isHiddenByLogic}
placement="top"
label="This field may be hidden by your form logic"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

may be or is? Asking because the Tooltip's only control on its hidden state is isHiddenByLogic 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hahah. this was the copy that was agreed upon. In actual fact, the field is hidden by logic by default (i.e. on first load of the form). Could change the variable name to isDefaultHiddenByLogic if that feels better?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nah, let's keep it. We can revisit later if needed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

may is correct, since a logic match will show the field yea?

>
<Fade in={isActive}>
<chakra.button
display="flex"
tabIndex={isActive ? 0 : -1}
{...provided.dragHandleProps}
borderRadius="4px"
_focus={{
boxShadow: snapshot.isDragging
? undefined
: '0 0 0 2px var(--chakra-colors-neutral-500)',
}}
>
<Icon
transition="color 0.2s ease"
_hover={{
color: 'secondary.300',
}}
color={
snapshot.isDragging ? 'secondary.300' : 'secondary.200'
}
as={BiGridHorizontal}
fontSize="1.5rem"
/>
</chakra.button>
</Fade>
<Box
px={{ base: '0.75rem', md: '1.5rem' }}
pb={{ base: '0.75rem', md: '1.5rem' }}
w="100%"
pointerEvents={isActive ? undefined : 'none'}
<Flex
// Offset for focus boxShadow
my="2px"
// Focusable
tabIndex={0}
role="button"
cursor={isActive ? 'initial' : 'pointer'}
bg="white"
transition="background 0.2s ease"
_hover={{ bg: isDraggingOver ? 'white' : 'secondary.100' }}
borderRadius="4px"
outline="none"
{...(isActive ? { 'data-active': true } : {})}
_focusWithin={{
boxShadow: snapshot.isDragging
? 'md'
: '0 0 0 2px var(--chakra-colors-primary-500) !important',
}}
_active={{
bg: 'secondary.100',
boxShadow: snapshot.isDragging
? 'md'
: '0 0 0 2px var(--chakra-colors-primary-500)',
}}
flexDir="column"
align="center"
onClick={handleFieldClick}
onKeyDown={handleKeydown}
ref={ref}
>
<FormProvider {...formMethods}>
<MemoFieldRow field={field} colorTheme={colorTheme} />
</FormProvider>
</Box>
<Collapse in={isActive} style={{ width: '100%' }}>
<Flex
<Fade in={isActive}>
<chakra.button
display="flex"
tabIndex={isActive ? 0 : -1}
{...provided.dragHandleProps}
borderRadius="4px"
_focus={{
boxShadow: snapshot.isDragging
? undefined
: '0 0 0 2px var(--chakra-colors-neutral-500)',
}}
>
<Icon
transition="color 0.2s ease"
_hover={{
color: 'secondary.300',
}}
color={
snapshot.isDragging ? 'secondary.300' : 'secondary.200'
}
as={BiGridHorizontal}
fontSize="1.5rem"
/>
</chakra.button>
</Fade>
<Box
px={{ base: '0.75rem', md: '1.5rem' }}
flex={1}
borderTop="1px solid var(--chakra-colors-neutral-300)"
justify="flex-end"
pb={{ base: '0.75rem', md: '1.5rem' }}
w="100%"
pointerEvents={isActive ? undefined : 'none'}
opacity={isActive || !isHiddenByLogic ? '100%' : '30%'}
>
<ButtonGroup
variant="clear"
colorScheme="secondary"
spacing={0}
<FormProvider {...formMethods}>
<MemoFieldRow field={field} colorTheme={colorTheme} />
</FormProvider>
</Box>
<Collapse in={isActive} style={{ width: '100%' }}>
<Flex
px={{ base: '0.75rem', md: '1.5rem' }}
flex={1}
borderTop="1px solid var(--chakra-colors-neutral-300)"
justify="flex-end"
>
{isMobile ? (
<IconButton
variant="clear"
colorScheme="secondary"
aria-label="Edit field"
icon={<BiCog fontSize="1.25rem" />}
onClick={handleEditFieldClick}
/>
) : null}
{
// Fields which are not yet created cannot be duplicated
stateData.state !== BuildFieldState.CreatingField && (
<ButtonGroup
variant="clear"
colorScheme="secondary"
spacing={0}
>
{isMobile ? (
<IconButton
aria-label="Duplicate field"
isDisabled={isAnyMutationLoading}
onClick={handleDuplicateClick}
isLoading={duplicateFieldMutation.isLoading}
icon={<BiDuplicate fontSize="1.25rem" />}
variant="clear"
colorScheme="secondary"
aria-label="Edit field"
icon={<BiCog fontSize="1.25rem" />}
onClick={handleEditFieldClick}
/>
)
}
<IconButton
colorScheme="danger"
aria-label="Delete field"
icon={<BiTrash fontSize="1.25rem" />}
onClick={handleDeleteClick}
isLoading={deleteFieldMutation.isLoading}
isDisabled={isAnyMutationLoading}
/>
</ButtonGroup>
</Flex>
</Collapse>
</Flex>
) : null}
{
// Fields which are not yet created cannot be duplicated
stateData.state !== BuildFieldState.CreatingField && (
<IconButton
aria-label="Duplicate field"
isDisabled={isAnyMutationLoading}
onClick={handleDuplicateClick}
isLoading={duplicateFieldMutation.isLoading}
icon={<BiDuplicate fontSize="1.25rem" />}
/>
)
}
<IconButton
colorScheme="danger"
aria-label="Delete field"
icon={<BiTrash fontSize="1.25rem" />}
onClick={handleDeleteClick}
isLoading={deleteFieldMutation.isLoading}
isDisabled={isAnyMutationLoading}
/>
</ButtonGroup>
</Flex>
</Collapse>
</Flex>
</Tooltip>
</Box>
)}
</Draggable>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { useMemo } from 'react'
import { Droppable } from 'react-beautiful-dnd'
import { Box, Flex, FlexProps, Stack } from '@chakra-ui/react'

import Button from '~components/Button'

import { getVisibleFieldIds } from '~features/logic/utils'
import { useBgColor } from '~features/public-form/components/PublicFormWrapper'

import { useCreatePageSidebar } from '../../common/CreatePageSidebarContext'
import { useAdminFormLogic } from '../../logic/hooks/useAdminFormLogic'
import { FIELD_LIST_DROP_ID } from '../constants'
import { DndPlaceholderProps } from '../types'
import {
Expand All @@ -29,8 +32,17 @@ export const FormBuilder = ({
...props
}: FormBuilderProps): JSX.Element => {
const { builderFields } = useBuilderFields()
const { formLogics } = useAdminFormLogic()
const { handleBuilderClick } = useCreatePageSidebar()
const setEditEndPage = useBuilderAndDesignStore(setToEditEndPageSelector)
const visibleFieldIds = useMemo(
() =>
getVisibleFieldIds(
{}, // Assume form has no inputs yet.
{ formFields: builderFields ?? [], formLogics: formLogics ?? [] },
),
[builderFields, formLogics],
)

const bg = useBgColor({ colorTheme: useDesignColorTheme() })

Expand Down Expand Up @@ -71,6 +83,7 @@ export const FormBuilder = ({
>
<BuilderFields
fields={builderFields}
visibleFieldIds={visibleFieldIds}
isDraggingOver={snapshot.isDraggingOver}
/>
{provided.placeholder}
Expand Down
26 changes: 25 additions & 1 deletion frontend/src/mocks/msw/handlers/admin-form/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ import {
RatingShape,
TableFieldDto,
} from '~shared/types/field'
import { FormLogic } from '~shared/types/form'
import {
FormLogic,
LogicConditionState,
LogicIfValue,
LogicType,
} from '~shared/types/form'
import {
AdminFormDto,
AdminFormViewDto,
Expand Down Expand Up @@ -546,6 +551,25 @@ export const MOCK_FORM_FIELDS_WITH_MYINFO = [
...MOCK_MYINFO_FIELDS,
]

export const MOCK_FORM_LOGICS = [
// Note: this logic is actually invalid since the if field cannot be a show
// field at the same time. But it's fine just for the purposes of displaying
// the hidden view.
{
show: MOCK_FORM_FIELDS_WITH_MYINFO.map((f) => f._id),
_id: '620115f74ad4f00012900a8c',
logicType: LogicType.ShowFields as const,
conditions: [
{
ifValueType: LogicIfValue.SingleSelect,
field: '5da04eb5e397fc0013f63c7e',
state: LogicConditionState.Equal,
value: 'Yes',
},
],
},
]

export const createMockForm = (
props: Partial<AdminFormDto> = {},
): AdminFormViewDto => {
Expand Down