Skip to content

Commit

Permalink
refactor: [M3-8637] - Make EditableText UI component pure (linode#1…
Browse files Browse the repository at this point in the history
…1333)

* initial idea

* improve comment

* fix spelling

* fix spelling

* use `to` rather than `link`

* use `to` rather than `link`

* fix comment

* use the new prop in Cloud Manager

* impliment some changes from Jaalah's component

* add changeset

* thanks @abailly-akamai 🙏

* clean up a bit

* final clean up

---------

Co-authored-by: Banks Nussman <banks@nussman.us>
  • Loading branch information
bnussman-akamai and bnussman authored Dec 18, 2024
1 parent bb4c965 commit b494346
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { EditableText, H1Header } from '@linode/ui';
import { styled } from '@mui/material';

import type { EditableTextProps } from '@linode/ui';

export const StyledDiv = styled('div', { label: 'StyledDiv' })({
display: 'flex',
flexDirection: 'column',
});

export const StyledEditableText = styled(EditableText, {
label: 'StyledEditableText',
})(({ theme }) => ({
})<EditableTextProps>(({ theme }) => ({
'& > div': {
width: 250,
},
Expand Down
13 changes: 11 additions & 2 deletions packages/manager/src/components/Breadcrumb/FinalCrumb.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import * as React from 'react';

import { Link } from '../Link';
import {
StyledDiv,
StyledEditableText,
StyledH1Header,
} from './FinalCrumb.styles';
import { EditableProps, LabelProps } from './types';

import type { EditableProps, LabelProps } from './types';

interface Props {
crumb: string;
Expand All @@ -22,17 +24,24 @@ export const FinalCrumb = React.memo((props: Props) => {
onEditHandlers,
} = props;

const linkProps = labelOptions?.linkTo
? {
LinkComponent: Link,
labelLink: labelOptions.linkTo,
}
: {};

if (onEditHandlers) {
return (
<StyledEditableText
data-qa-editable-text
disabledBreadcrumbEditButton={disabledBreadcrumbEditButton}
errorText={onEditHandlers.errorText}
handleAnalyticsEvent={onEditHandlers.handleAnalyticsEvent}
labelLink={labelOptions && labelOptions.linkTo}
onCancel={onEditHandlers.onCancel}
onEdit={onEditHandlers.onEdit}
text={onEditHandlers.editableTextTitle}
{...linkProps}
/>
);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/manager/src/components/Link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export interface LinkProps extends Omit<_LinkProps, 'to'> {
* @example "/profile/display"
* @example "https://linode.com"
*/
to: TanStackLinkProps['to'] | (string & {});
to: Exclude<TanStackLinkProps['to'] | (string & {}), null | undefined>;
}

/**
Expand Down
5 changes: 5 additions & 0 deletions packages/ui/.changeset/pr-11333-changed-1733176201231.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/ui": Changed
---

Update `EditableText` to not use `react-router-dom` and accept a `LinkComponent` prop ([#11333](https://github.com/linode/manager/pull/11333))
20 changes: 20 additions & 0 deletions packages/ui/src/components/EditableText/EditableText.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,26 @@ export const WithSuffix: Story = {
},
};

/**
* Pretend this is `react-router-dom`'s Link component.
* This is just an example to show usage with `EditableText`
*/
const Link = (
props: React.PropsWithChildren<{ className?: string; to?: string }>
) => {
return <a {...props} href={props.to} target="_blank" />;
};

export const WithCustomLinkComponent: Story = {
args: {
LinkComponent: Link,
labelLink: 'https://linode.com',
onCancel: action('onCancel'),
text: 'I have a link',
},
render: (args) => <EditableText {...args} />,
};

const meta: Meta<typeof EditableText> = {
component: EditableText,
title: 'Components/Input/Editable Text',
Expand Down
53 changes: 41 additions & 12 deletions packages/ui/src/components/EditableText/EditableText.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import Check from '@mui/icons-material/Check';
import Close from '@mui/icons-material/Close';
import Edit from '@mui/icons-material/Edit';
import * as React from 'react';
import { Link } from 'react-router-dom';
import React from 'react';
import { makeStyles } from 'tss-react/mui';

import { Button } from '../Button';
Expand All @@ -12,6 +11,7 @@ import { TextField } from '../TextField';

import type { TextFieldProps } from '../TextField';
import type { Theme } from '@mui/material/styles';
import type { PropsWithChildren } from 'react';

const useStyles = makeStyles<void, 'editIcon' | 'icon'>()(
(theme: Theme, _params, classes) => ({
Expand Down Expand Up @@ -102,26 +102,31 @@ const useStyles = makeStyles<void, 'editIcon' | 'icon'>()(
})
);

interface Props {
interface BaseProps extends Omit<TextFieldProps, 'label'> {
/**
* The class name to apply to the container
*/
className?: string;
/**
* Whether to disable the Breadcrumb edit button
*/
disabledBreadcrumbEditButton?: boolean;
/**
* The error text to display
*/
errorText?: string;
/**
* Send event analytics
*/
handleAnalyticsEvent?: () => void;
/**
* Optional link for the text when it is not in editing mode
*/
labelLink?: string;
/**
* Function to cancel editing and restore text to previous text
*/
onCancel: () => void;
/**
* The function to handle saving edited text
*/
onEdit: (text: string) => Promise<any>;
onEdit: (_text: string) => Promise<void>;
/**
* The text inside the textbox
*/
Expand All @@ -132,14 +137,38 @@ interface Props {
textSuffix?: string;
}

interface PassThroughProps extends Props, Omit<TextFieldProps, 'label'> {}
interface PropsWithoutLink extends BaseProps {
LinkComponent?: never;
labelLink?: never;
}

interface PropsWithLink extends BaseProps {
/**
* A custom Link component that is required when passing a `labelLink` prop
*
* The component you pass must accept `className`, `to`, and `children` as props
* - `to` is just the `labelLink` prop forwarded to this Link component
* - `className` should be passed to your Link so that it has the correct styles
* - `children` contains the link's text/children
*/
LinkComponent: React.ComponentType<
PropsWithChildren<{ className?: string; to: string }>
>;
/**
* Optional link for the text when it is not in editing mode
*/
labelLink: string;
}

export type EditableTextProps = PropsWithLink | PropsWithoutLink;

export const EditableText = (props: PassThroughProps) => {
export const EditableText = (props: EditableTextProps) => {
const { classes } = useStyles();

const [isEditing, setIsEditing] = React.useState(Boolean(props.errorText));
const [text, setText] = React.useState(props.text);
const {
LinkComponent,
className,
disabledBreadcrumbEditButton,
errorText,
Expand Down Expand Up @@ -220,9 +249,9 @@ export const EditableText = (props: PassThroughProps) => {
data-testid={'editable-text'}
>
{!!labelLink ? (
<Link className={classes.underlineOnHover} to={labelLink!}>
<LinkComponent className={classes.underlineOnHover} to={labelLink}>
{labelText}
</Link>
</LinkComponent>
) : (
labelText
)}
Expand Down

0 comments on commit b494346

Please sign in to comment.