Open showpage on workflow creation (#9714)
- Created an new component state `isRecordEditableNameRenamingComponentState` - Updated `useCreateNewTableRecord` to open the ShowPage on workflow creation - Refactored `RecordEditableName` and its components to remove the useEffect (This was causing the recordName state to be updated after the focus on `NavigationDrawerInput`, but we want the text so be selected after the update). - Introduced a new component `EditableBreadcrumbItem` - Created an autosizing text input: This is done by a hack using a span inside a div and the input position is set to absolute and takes the size of the div. There are two problems that I didn't manage to fix: If the text is too long, the title overflows, and the letter spacing is different between the span and the input creating a small offset. https://github.com/user-attachments/assets/4aa1e177-7458-4691-b0c8-96567b482206 New text input component: https://github.com/user-attachments/assets/94565546-fe2b-457d-a1d8-907007e0e2ce
This commit is contained in:
@ -1,3 +1,4 @@
|
||||
import { InputLabel } from '@/ui/input/components/InputLabel';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import {
|
||||
@ -10,28 +11,37 @@ import {
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { IconComponent, IconEye, IconEyeOff, RGBA } from 'twenty-ui';
|
||||
import {
|
||||
ComputeNodeDimensions,
|
||||
IconComponent,
|
||||
IconEye,
|
||||
IconEyeOff,
|
||||
RGBA,
|
||||
} from 'twenty-ui';
|
||||
import { useCombinedRefs } from '~/hooks/useCombinedRefs';
|
||||
import { turnIntoEmptyStringIfWhitespacesOnly } from '~/utils/string/turnIntoEmptyStringIfWhitespacesOnly';
|
||||
import { InputLabel } from './InputLabel';
|
||||
|
||||
const StyledContainer = styled.div<
|
||||
Pick<TextInputV2ComponentProps, 'fullWidth'>
|
||||
>`
|
||||
box-sizing: border-box;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
width: ${({ fullWidth }) => (fullWidth ? `100%` : 'auto')};
|
||||
`;
|
||||
|
||||
const StyledInputContainer = styled.div`
|
||||
background-color: inherit;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const StyledInput = styled.input<
|
||||
Pick<TextInputV2ComponentProps, 'fullWidth' | 'LeftIcon' | 'error'>
|
||||
Pick<
|
||||
TextInputV2ComponentProps,
|
||||
'LeftIcon' | 'error' | 'sizeVariant' | 'width'
|
||||
>
|
||||
>`
|
||||
background-color: ${({ theme }) => theme.background.transparent.lighter};
|
||||
border: 1px solid
|
||||
@ -44,12 +54,14 @@ const StyledInput = styled.input<
|
||||
flex-grow: 1;
|
||||
font-family: ${({ theme }) => theme.font.family};
|
||||
font-weight: ${({ theme }) => theme.font.weight.regular};
|
||||
height: 32px;
|
||||
height: ${({ sizeVariant }) => (sizeVariant === 'sm' ? '20px' : '32px')};
|
||||
outline: none;
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
padding: ${({ theme, sizeVariant }) =>
|
||||
sizeVariant === 'sm' ? `${theme.spacing(2)} 0` : theme.spacing(2)};
|
||||
padding-left: ${({ theme, LeftIcon }) =>
|
||||
LeftIcon ? `calc(${theme.spacing(4)} + 16px)` : theme.spacing(2)};
|
||||
width: 100%;
|
||||
LeftIcon ? `px` : theme.spacing(2)};
|
||||
width: ${({ theme, width }) =>
|
||||
width ? `calc(${width}px + ${theme.spacing(5)})` : '100%'};
|
||||
|
||||
&::placeholder,
|
||||
&::-webkit-input-placeholder {
|
||||
@ -111,6 +123,8 @@ const StyledTrailingIcon = styled.div`
|
||||
|
||||
const INPUT_TYPE_PASSWORD = 'password';
|
||||
|
||||
export type TextInputV2Size = 'sm' | 'md';
|
||||
|
||||
export type TextInputV2ComponentProps = Omit<
|
||||
InputHTMLAttributes<HTMLInputElement>,
|
||||
'onChange' | 'onKeyDown'
|
||||
@ -123,11 +137,15 @@ export type TextInputV2ComponentProps = Omit<
|
||||
noErrorHelper?: boolean;
|
||||
RightIcon?: IconComponent;
|
||||
LeftIcon?: IconComponent;
|
||||
autoGrow?: boolean;
|
||||
onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
|
||||
onBlur?: FocusEventHandler<HTMLInputElement>;
|
||||
dataTestId?: string;
|
||||
sizeVariant?: TextInputV2Size;
|
||||
};
|
||||
|
||||
type TextInputV2WithAutoGrowWrapperProps = TextInputV2ComponentProps;
|
||||
|
||||
const TextInputV2Component = (
|
||||
{
|
||||
className,
|
||||
@ -138,6 +156,7 @@ const TextInputV2Component = (
|
||||
onBlur,
|
||||
onKeyDown,
|
||||
fullWidth,
|
||||
width,
|
||||
error,
|
||||
noErrorHelper = false,
|
||||
required,
|
||||
@ -150,6 +169,7 @@ const TextInputV2Component = (
|
||||
LeftIcon,
|
||||
autoComplete,
|
||||
maxLength,
|
||||
sizeVariant = 'md',
|
||||
dataTestId,
|
||||
}: TextInputV2ComponentProps,
|
||||
// eslint-disable-next-line @nx/workspace-component-props-naming
|
||||
@ -183,8 +203,10 @@ const TextInputV2Component = (
|
||||
</StyledTrailingIcon>
|
||||
</StyledLeftIconContainer>
|
||||
)}
|
||||
|
||||
<StyledInput
|
||||
id={inputId}
|
||||
width={width}
|
||||
data-testid={dataTestId}
|
||||
autoComplete={autoComplete || 'off'}
|
||||
ref={combinedRef}
|
||||
@ -207,8 +229,10 @@ const TextInputV2Component = (
|
||||
LeftIcon,
|
||||
maxLength,
|
||||
error,
|
||||
sizeVariant,
|
||||
}}
|
||||
/>
|
||||
|
||||
<StyledTrailingIconContainer {...{ error }}>
|
||||
{!error && type === INPUT_TYPE_PASSWORD && (
|
||||
<StyledTrailingIcon
|
||||
@ -236,4 +260,22 @@ const TextInputV2Component = (
|
||||
);
|
||||
};
|
||||
|
||||
export const TextInputV2 = forwardRef(TextInputV2Component);
|
||||
const TextInputV2WithAutoGrowWrapper = (
|
||||
props: TextInputV2WithAutoGrowWrapperProps,
|
||||
) => (
|
||||
<>
|
||||
{props.autoGrow ? (
|
||||
<ComputeNodeDimensions node={props.value || props.placeholder}>
|
||||
{(nodeDimensions) => (
|
||||
// eslint-disable-next-line
|
||||
<TextInputV2Component {...props} width={nodeDimensions?.width} />
|
||||
)}
|
||||
</ComputeNodeDimensions>
|
||||
) : (
|
||||
// eslint-disable-next-line
|
||||
<TextInputV2Component {...props} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
export const TextInputV2 = forwardRef(TextInputV2WithAutoGrowWrapper);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { useState } from 'react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import {
|
||||
@ -40,3 +40,19 @@ export const Filled: Story = {
|
||||
export const Disabled: Story = {
|
||||
args: { disabled: true, value: 'Tim' },
|
||||
};
|
||||
|
||||
export const AutoGrow: Story = {
|
||||
args: { autoGrow: true, value: 'Tim' },
|
||||
};
|
||||
|
||||
export const AutoGrowWithPlaceholder: Story = {
|
||||
args: { autoGrow: true, placeholder: 'Tim' },
|
||||
};
|
||||
|
||||
export const Small: Story = {
|
||||
args: { sizeVariant: 'sm', value: 'Tim' },
|
||||
};
|
||||
|
||||
export const AutoGrowSmall: Story = {
|
||||
args: { autoGrow: true, sizeVariant: 'sm', value: 'Tim' },
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user