[Permissions] Force open title input for role label when empty (#12710)
- Fix empty title in breadcrumb - Enforce role label input open if empty
This commit is contained in:
@ -68,7 +68,7 @@ export const SettingsRoleSettings = ({
|
|||||||
label: value,
|
label: value,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
placeholder={t`Role name`}
|
placeholder={t`Untitled Role`}
|
||||||
disabled={!isEditable}
|
disabled={!isEditable}
|
||||||
/>
|
/>
|
||||||
</StyledInputsContainer>
|
</StyledInputsContainer>
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { SettingsRoleAssignment } from '@/settings/roles/role-assignment/compone
|
|||||||
import { SettingsRolePermissions } from '@/settings/roles/role-permissions/components/SettingsRolePermissions';
|
import { SettingsRolePermissions } from '@/settings/roles/role-permissions/components/SettingsRolePermissions';
|
||||||
import { SettingsRoleSettings } from '@/settings/roles/role-settings/components/SettingsRoleSettings';
|
import { SettingsRoleSettings } from '@/settings/roles/role-settings/components/SettingsRoleSettings';
|
||||||
import { SettingsRoleLabelContainer } from '@/settings/roles/role/components/SettingsRoleLabelContainer';
|
import { SettingsRoleLabelContainer } from '@/settings/roles/role/components/SettingsRoleLabelContainer';
|
||||||
|
import { SettingsRoleLabelContainerEffect } from '@/settings/roles/role/components/SettingsRoleLabelContainerEffect';
|
||||||
import { SETTINGS_ROLE_DETAIL_TABS } from '@/settings/roles/role/constants/SettingsRoleDetailTabs';
|
import { SETTINGS_ROLE_DETAIL_TABS } from '@/settings/roles/role/constants/SettingsRoleDetailTabs';
|
||||||
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||||
import { settingsPersistedRoleFamilyState } from '@/settings/roles/states/settingsPersistedRoleFamilyState';
|
import { settingsPersistedRoleFamilyState } from '@/settings/roles/states/settingsPersistedRoleFamilyState';
|
||||||
@ -19,6 +20,7 @@ import { TabList } from '@/ui/layout/tab-list/components/TabList';
|
|||||||
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
|
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { getOperationName } from '@apollo/client/utilities';
|
import { getOperationName } from '@apollo/client/utilities';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
@ -52,6 +54,10 @@ const ROLE_BASIC_KEYS: Array<keyof Role> = [
|
|||||||
'canDestroyAllObjectRecords',
|
'canDestroyAllObjectRecords',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const StyledUntitledRole = styled.span`
|
||||||
|
color: ${({ theme }) => theme.font.color.tertiary};
|
||||||
|
`;
|
||||||
|
|
||||||
export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
|
export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
|
||||||
const activeTabId = useRecoilComponentValueV2(
|
const activeTabId = useRecoilComponentValueV2(
|
||||||
activeTabIdComponentState,
|
activeTabIdComponentState,
|
||||||
@ -275,7 +281,12 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SubMenuTopBarContainer
|
<SubMenuTopBarContainer
|
||||||
title={<SettingsRoleLabelContainer roleId={roleId} />}
|
title={
|
||||||
|
<>
|
||||||
|
<SettingsRoleLabelContainer roleId={roleId} />
|
||||||
|
<SettingsRoleLabelContainerEffect roleId={roleId} />
|
||||||
|
</>
|
||||||
|
}
|
||||||
links={[
|
links={[
|
||||||
{
|
{
|
||||||
children: 'Workspace',
|
children: 'Workspace',
|
||||||
@ -286,14 +297,19 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
|
|||||||
href: getSettingsPath(SettingsPath.Roles),
|
href: getSettingsPath(SettingsPath.Roles),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
children: settingsDraftRole.label,
|
children:
|
||||||
|
isDefined(settingsDraftRole.label) &&
|
||||||
|
settingsDraftRole.label !== '' ? (
|
||||||
|
settingsDraftRole.label
|
||||||
|
) : (
|
||||||
|
<StyledUntitledRole>{t`Untitled Role`}</StyledUntitledRole>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
actionButton={
|
actionButton={
|
||||||
isRoleEditable &&
|
isRoleEditable && isDirty ? (
|
||||||
isDirty && (
|
|
||||||
<SaveAndCancelButtons onSave={handleSave} onCancel={handleCancel} />
|
<SaveAndCancelButtons onSave={handleSave} onCancel={handleCancel} />
|
||||||
)
|
) : null
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<SettingsPageContainer>
|
<SettingsPageContainer>
|
||||||
|
|||||||
@ -29,6 +29,8 @@ export const SettingsRoleLabelContainer = ({
|
|||||||
settingsDraftRoleFamilyState(roleId),
|
settingsDraftRoleFamilyState(roleId),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const titleInputInstanceId = `settings-role-label-${roleId}`;
|
||||||
|
|
||||||
const handleChange = (newValue: string) => {
|
const handleChange = (newValue: string) => {
|
||||||
setSettingsDraftRole({
|
setSettingsDraftRole({
|
||||||
...settingsDraftRole,
|
...settingsDraftRole,
|
||||||
@ -43,8 +45,9 @@ export const SettingsRoleLabelContainer = ({
|
|||||||
sizeVariant="md"
|
sizeVariant="md"
|
||||||
value={settingsDraftRole.label}
|
value={settingsDraftRole.label}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
placeholder={t`Role name`}
|
placeholder={t`Untitled Role`}
|
||||||
hotkeyScope={ROLE_LABEL_EDIT_HOTKEY_SCOPE}
|
hotkeyScope={ROLE_LABEL_EDIT_HOTKEY_SCOPE}
|
||||||
|
instanceId={titleInputInstanceId}
|
||||||
/>
|
/>
|
||||||
</StyledHeaderTitle>
|
</StyledHeaderTitle>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -0,0 +1,34 @@
|
|||||||
|
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||||
|
import { titleInputComponentState } from '@/ui/input/states/titleInputComponentState';
|
||||||
|
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
|
export const SettingsRoleLabelContainerEffect = ({
|
||||||
|
roleId,
|
||||||
|
}: {
|
||||||
|
roleId: string;
|
||||||
|
}) => {
|
||||||
|
const settingsDraftRole = useRecoilValue(
|
||||||
|
settingsDraftRoleFamilyState(roleId),
|
||||||
|
);
|
||||||
|
const titleInputInstanceId = `settings-role-label-${roleId}`;
|
||||||
|
|
||||||
|
const [isTitleInputOpen, setIsTitleInputOpen] = useRecoilComponentStateV2(
|
||||||
|
titleInputComponentState,
|
||||||
|
titleInputInstanceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (settingsDraftRole.label === '' && !isTitleInputOpen) {
|
||||||
|
setIsTitleInputOpen(true);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
settingsDraftRole.label,
|
||||||
|
setIsTitleInputOpen,
|
||||||
|
titleInputInstanceId,
|
||||||
|
isTitleInputOpen,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return <></>;
|
||||||
|
};
|
||||||
@ -6,7 +6,9 @@ import { useRef, useState } from 'react';
|
|||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
import { useRegisterInputEvents } from '@/object-record/record-field/meta-types/input/hooks/useRegisterInputEvents';
|
import { useRegisterInputEvents } from '@/object-record/record-field/meta-types/input/hooks/useRegisterInputEvents';
|
||||||
|
import { titleInputComponentState } from '@/ui/input/states/titleInputComponentState';
|
||||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||||
|
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { OverflowingTextWithTooltip } from 'twenty-ui/display';
|
import { OverflowingTextWithTooltip } from 'twenty-ui/display';
|
||||||
|
|
||||||
@ -25,6 +27,7 @@ type InputProps = {
|
|||||||
|
|
||||||
export type TitleInputProps = {
|
export type TitleInputProps = {
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
instanceId: string;
|
||||||
} & InputProps;
|
} & InputProps;
|
||||||
|
|
||||||
const StyledDiv = styled.div<{
|
const StyledDiv = styled.div<{
|
||||||
@ -142,14 +145,18 @@ export const TitleInput = ({
|
|||||||
onClickOutside,
|
onClickOutside,
|
||||||
onTab,
|
onTab,
|
||||||
onShiftTab,
|
onShiftTab,
|
||||||
|
instanceId,
|
||||||
}: TitleInputProps) => {
|
}: TitleInputProps) => {
|
||||||
const [isOpened, setIsOpened] = useState(false);
|
const [isTitleInputOpen, setIsTitleInputOpen] = useRecoilComponentStateV2(
|
||||||
|
titleInputComponentState,
|
||||||
|
instanceId,
|
||||||
|
);
|
||||||
|
|
||||||
const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope();
|
const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isOpened ? (
|
{isTitleInputOpen ? (
|
||||||
<Input
|
<Input
|
||||||
sizeVariant={sizeVariant}
|
sizeVariant={sizeVariant}
|
||||||
value={value}
|
value={value}
|
||||||
@ -161,7 +168,7 @@ export const TitleInput = ({
|
|||||||
onClickOutside={onClickOutside}
|
onClickOutside={onClickOutside}
|
||||||
onTab={onTab}
|
onTab={onTab}
|
||||||
onShiftTab={onShiftTab}
|
onShiftTab={onShiftTab}
|
||||||
setIsOpened={setIsOpened}
|
setIsOpened={setIsTitleInputOpen}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<StyledDiv
|
<StyledDiv
|
||||||
@ -169,7 +176,7 @@ export const TitleInput = ({
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!disabled) {
|
if (!disabled) {
|
||||||
setIsOpened(true);
|
setIsTitleInputOpen(true);
|
||||||
setHotkeyScopeAndMemorizePreviousScope({
|
setHotkeyScopeAndMemorizePreviousScope({
|
||||||
scope: hotkeyScope,
|
scope: hotkeyScope,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -11,6 +11,7 @@ const meta: Meta<typeof TitleInput> = {
|
|||||||
placeholder: 'Enter title',
|
placeholder: 'Enter title',
|
||||||
hotkeyScope: 'titleInput',
|
hotkeyScope: 'titleInput',
|
||||||
sizeVariant: 'md',
|
sizeVariant: 'md',
|
||||||
|
instanceId: 'title-input-story',
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
hotkeyScope: { control: false },
|
hotkeyScope: { control: false },
|
||||||
|
|||||||
@ -0,0 +1,4 @@
|
|||||||
|
import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext';
|
||||||
|
|
||||||
|
export const TitleInputComponentInstanceContext =
|
||||||
|
createComponentInstanceContext();
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
import { TitleInputComponentInstanceContext } from '@/ui/input/contexts/titleInputComponentInstanceContext';
|
||||||
|
import { titleInputComponentState } from '@/ui/input/states/titleInputComponentState';
|
||||||
|
import { createComponentSelectorV2 } from '@/ui/utilities/state/component-state/utils/createComponentSelectorV2';
|
||||||
|
|
||||||
|
export const titleInputComponentSelector = createComponentSelectorV2({
|
||||||
|
key: 'titleInputComponentSelector',
|
||||||
|
get:
|
||||||
|
({ instanceId }) =>
|
||||||
|
({ get }) => {
|
||||||
|
const isTitleInputOpen = get(
|
||||||
|
titleInputComponentState.atomFamily({ instanceId }),
|
||||||
|
);
|
||||||
|
|
||||||
|
return isTitleInputOpen;
|
||||||
|
},
|
||||||
|
componentInstanceContext: TitleInputComponentInstanceContext,
|
||||||
|
});
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
import { TitleInputComponentInstanceContext } from '@/ui/input/contexts/titleInputComponentInstanceContext';
|
||||||
|
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||||
|
|
||||||
|
export const titleInputComponentState = createComponentStateV2<boolean>({
|
||||||
|
key: 'titleInputComponentState',
|
||||||
|
defaultValue: false,
|
||||||
|
componentInstanceContext: TitleInputComponentInstanceContext,
|
||||||
|
});
|
||||||
@ -114,6 +114,7 @@ export const WorkflowStepHeader = ({
|
|||||||
onClickOutside={saveTitle}
|
onClickOutside={saveTitle}
|
||||||
onTab={saveTitle}
|
onTab={saveTitle}
|
||||||
onShiftTab={saveTitle}
|
onShiftTab={saveTitle}
|
||||||
|
instanceId="workflow-step-title"
|
||||||
/>
|
/>
|
||||||
</StyledHeaderTitle>
|
</StyledHeaderTitle>
|
||||||
<StyledHeaderType>{headerType}</StyledHeaderType>
|
<StyledHeaderType>{headerType}</StyledHeaderType>
|
||||||
|
|||||||
Reference in New Issue
Block a user