Skip to content

Commit

Permalink
feat(suite): Add EditableText to product-component (#16704)
Browse files Browse the repository at this point in the history
  • Loading branch information
jvaclavik authored Jan 31, 2025
1 parent 678de31 commit 20f19cd
Show file tree
Hide file tree
Showing 8 changed files with 478 additions and 3 deletions.
5 changes: 3 additions & 2 deletions packages/components/src/components/Box/Box.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,14 @@ const Container = styled.div<TransientProps<AllowedFrameProps>>`
export type BoxProps = AllowedFrameProps & {
children: React.ReactNode;
'data-testid'?: string;
as?: React.ElementType;
};

export const Box = ({ children, 'data-testid': dataTestId, ...rest }: BoxProps) => {
export const Box = ({ children, 'data-testid': dataTestId, as = 'div', ...rest }: BoxProps) => {
const frameProps = pickAndPrepareFrameProps(rest, allowedBoxFrameProps);

return (
<Container data-testid={dataTestId} {...frameProps}>
<Container as={as} data-testid={dataTestId} {...frameProps}>
{children}
</Container>
);
Expand Down
9 changes: 8 additions & 1 deletion packages/components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,14 @@ export { SubTabs, type SubTabsProps } from './components/SubTabs/SubTabs';
export { VirtualizedList } from './components/VirtualizedList/VirtualizedList';
export { List, type ListProps } from './components/List/List';
export { StoryColumn, StoryWrapper } from './support/Story';
export { type Margin } from './utils/frameProps';
export {
type Margin,
type FrameProps,
type FramePropsKeys,
pickAndPrepareFrameProps,
withFrameProps,
getFramePropsStory,
} from './utils/frameProps';

export * from './constants/keyboardEvents';

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';

import styled from 'styled-components';

import { Badge, Box, IconButton, Row, Spinner, Tooltip, useElevation } from '@trezor/components';
import {
Elevation,
borders,
mapElevationToBorder,
spacings,
spacingsPx,
zIndices,
} from '@trezor/theme';

type ActionContainerProps = {
onEdit: () => void;
onDelete: () => void;
onSave: () => void;
onCancel: () => void;
isLoading?: boolean;
isEditable: boolean;
isJustSaved: boolean;
isDisabled?: boolean;
isHovered: boolean;
isDeleteButtonVisible: boolean;
};

const ActionsBackground = styled.div<{ $elevation: Elevation }>`
background: ${({ theme }) => mapElevationToBorder({ theme, $elevation: 2 })};
border-radius: ${borders.radii.full};
padding: ${spacingsPx.xxs};
margin-left: ${spacingsPx.xs};
`;

export const ActionsContainer = ({
onEdit,
onDelete,
onSave,
onCancel,
isLoading,
isEditable,
isJustSaved,
isDisabled,
isHovered,
isDeleteButtonVisible,
}: ActionContainerProps) => {
const { elevation } = useElevation();

return (
<Box
as="span"
position={{ type: 'absolute', top: 0, left: '100%' }}
height="100%"
zIndex={zIndices.tooltip}
cursor="pointer"
>
<Row alignItems="center" height="100%">
{isLoading ? (
<ActionsBackground $elevation={elevation}>
<Row gap={spacings.xxs}>
<Spinner size={20} />
<FormattedMessage id="TR_LOADING" defaultMessage="Loading" />
</Row>
</ActionsBackground>
) : (
<>
{!isJustSaved && isEditable && (
<ActionsBackground $elevation={elevation}>
<Row gap={spacings.xxs}>
<Tooltip
content={
<FormattedMessage
id="TR_CONFIRM"
defaultMessage="Confirm"
/>
}
hasArrow
delayShow={1000}
cursor="inherit"
>
<IconButton
icon="check"
size="tiny"
onClick={onSave}
isDisabled={isDisabled}
/>
</Tooltip>
<Tooltip
content={
<FormattedMessage
id="TR_CANCEL"
defaultMessage="Cancel"
/>
}
hasArrow
delayShow={1000}
cursor="inherit"
>
<IconButton
variant="destructive"
icon="x"
size="tiny"
onClick={onCancel}
isDisabled={isDisabled}
/>
</Tooltip>
</Row>
</ActionsBackground>
)}
{!isJustSaved && !isEditable && isHovered && (
<Row gap={spacings.xxs} margin={{ left: spacings.sm }}>
<IconButton
variant="tertiary"
icon="pencil"
size="tiny"
onClick={onEdit}
isDisabled={isDisabled}
/>
{isDeleteButtonVisible && (
<IconButton
variant="tertiary"
icon="x"
size="tiny"
onClick={onDelete}
isDisabled={isDisabled}
/>
)}
</Row>
)}
{isJustSaved && (
<Row gap={spacings.xxs} margin={{ left: spacings.sm }}>
<Badge icon="check" variant="primary">
<FormattedMessage id="TR_SAVED" defaultMessage="Saved" />
</Badge>
</Row>
)}
</>
)}
</Row>
</Box>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { IntlProvider } from 'react-intl';

import { action } from '@storybook/addon-actions';
import { Meta, StoryObj } from '@storybook/react';

import { Column, Row, Text, getFramePropsStory } from '@trezor/components';

import {
EditableText as EditableTextComponent,
EditableTextProps,
allowedEditableTextFrameProps,
} from './EditableText';

const meta: Meta = {
title: 'EditableText',
decorators: [
(Story: React.FC) => (
<IntlProvider locale="en">
<Story />
</IntlProvider>
),
],
component: EditableTextComponent,
} as Meta;
export default meta;

export const EditableText: StoryObj<EditableTextProps> = {
parameters: {
docs: {
description: {
component: `
A text component that can be edited inline.
## Features
- Click to edit
- Press Enter to save
- Press Escape to cancel
- Click outside to cancel
`,
},
},
},
render: ({ children, ...rest }: EditableTextProps) => (
<Column>
<Row gap={4}>
<Text typographyStyle="body">
<EditableTextComponent {...rest}>{children}</EditableTextComponent>
</Text>{' '}
<Text typographyStyle="body">Lorem ipsum dolor mucho</Text>
</Row>
<Text typographyStyle="titleMedium">
<EditableTextComponent {...rest}>{children}</EditableTextComponent>
</Text>
</Column>
),
args: {
children: 'hello',
maxWidth: undefined,
onSave: action('onSave'),
isLoading: false,
isDisabled: false,
...getFramePropsStory(allowedEditableTextFrameProps).args,
},
argTypes: {
maxWidth: {
control: {
type: 'text',
},
},
...getFramePropsStory(allowedEditableTextFrameProps).argTypes,
},
};
Loading

0 comments on commit 20f19cd

Please sign in to comment.