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: add support for extended nav and action components #1918

Merged
merged 1 commit into from
Apr 22, 2024
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
48 changes: 47 additions & 1 deletion src/components/Button/Button-v2.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { StoryObj, Meta } from '@storybook/react';
import React from 'react';
import { Button } from './Button-v2';
import { Button, type ButtonV2Props } from './Button-v2';
import { SIZES } from '../ClickableStyle';

export default {
Expand Down Expand Up @@ -186,3 +186,49 @@ export const IconLayouts: StoryObj<Args> = {
);
},
};

// Here, we introduce a special type extension to LinkProps, then use it in a
// composed component, to demonstrate the ability to offer custom props to a component
type ExtendArgs = ButtonV2Props<{ to: string }>;
function ExtendedButton(args: ExtendArgs) {
return (
// eslint-disable-next-line no-alert
<Button {...args} onClick={() => alert(`handle to value: ${args.to}`)} />
);
}

/**
* You can extend a component's props for use with libraries that aid navigation, e.g., react-dom-router, et al.
*
* Steps to use:
*
* * import `ButtonProps`
* * use the type param. to augment the types for `Button` with the libraries type, e.g., `type ExtendedProps = ButtonProps<typeof CustomButton>;`
* * Now export a new function component that uses the new prop type and returns a composed function
*
* When using this pattern, you likely want to also specify the library's Button component using `as`
*
* ```tsx
* type ExtendedProps = ButtonProps<typeof CustomButton>;
*
* export default function Button({children, ...other}: ExtendedProps) {
* return (
* <Button as={CustomButton} {...other}>
* {children}
* </Button>
* );
* }
* ```
*/
export const UsingExtendedLink: StoryObj<ExtendArgs> = {
render: (args) => (
<div>
Lorem ipsum dolor sit amet,{' '}
<ExtendedButton {...args} to="test">
consectetur adipiscing elit
</ExtendedButton>
. Morbi porta at ante quis molestie. Nam scelerisque id diam at iaculis.
Nullam sit amet iaculis erat. Nulla id tellus ante.{' '}
</div>
),
};
135 changes: 68 additions & 67 deletions src/components/Button/Button-v2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,73 +9,74 @@ import styles from './Button-v2.module.css';

type ButtonHTMLElementProps = React.ButtonHTMLAttributes<HTMLButtonElement>;

type ButtonV2Props = ButtonHTMLElementProps & {
// Component API
/**
* `Button` contents or label.
*/
children: string;
/**
* Determine the behavior of the button upon click:
* - **button** `Button` is a clickable button with no default behavior
* - **submit** `Button` is a clickable button that submits form data
* - **reset** `Button` is a clickable button that resets the form-data to its initial values
*/
type?: 'button' | 'reset' | 'submit';

// Design API
/**
* Sets the hierarchy rank of the button
*
* **Default is `"primary"`**.
*/
rank?: 'primary' | 'secondary' | 'tertiary';

/**
* The size of the button on screen
*/
size?: Extract<Size, 'sm' | 'md' | 'lg'>;

/**
* The variant of the default tertiary button.
*/
context?: 'default' | 'standalone';

/**
* Icon from the set of defined EDS icon set, when `iconLayout` is used.
*/
icon?: IconName;

/**
* Allows configuation of the icon's positioning within `Button`.
*
* - When set to a value besides `"none"`, an icon must be specified.
* - When `"icon-only"`, `aria-label` must be given a value.
*/
iconLayout?: 'none' | 'left' | 'right' | 'icon-only';

/**
* Status (color) variant for `Button`.
*
* **Default is `"default"`**.
*/
variant?: 'default' | 'critical' | 'inverse';

/**
* Whether the width of the button is set to the full layout.
*/
isFullWidth?: boolean;

/**
* Whether `Button` is set to disabled state (disables interaction and updates appearance).
*/
isDisabled?: boolean;

/**
* Loading state passed down from higher level used to trigger loader and text change.
*/
isLoading?: boolean;
};
export type ButtonV2Props<ExtendedElement = unknown> =
ButtonHTMLElementProps & {
// Component API
/**
* `Button` contents or label.
*/
children: string;
/**
* Determine the behavior of the button upon click:
* - **button** `Button` is a clickable button with no default behavior
* - **submit** `Button` is a clickable button that submits form data
* - **reset** `Button` is a clickable button that resets the form-data to its initial values
*/
type?: 'button' | 'reset' | 'submit';

// Design API
/**
* Sets the hierarchy rank of the button
*
* **Default is `"primary"`**.
*/
rank?: 'primary' | 'secondary' | 'tertiary';

/**
* The size of the button on screen
*/
size?: Extract<Size, 'sm' | 'md' | 'lg'>;

/**
* The variant of the default tertiary button.
*/
context?: 'default' | 'standalone';

/**
* Icon from the set of defined EDS icon set, when `iconLayout` is used.
*/
icon?: IconName;

/**
* Allows configuation of the icon's positioning within `Button`.
*
* - When set to a value besides `"none"`, an icon must be specified.
* - When `"icon-only"`, `aria-label` must be given a value.
*/
iconLayout?: 'none' | 'left' | 'right' | 'icon-only';

/**
* Status (color) variant for `Button`.
*
* **Default is `"default"`**.
*/
variant?: 'default' | 'critical' | 'inverse';

/**
* Whether the width of the button is set to the full layout.
*/
isFullWidth?: boolean;

/**
* Whether `Button` is set to disabled state (disables interaction and updates appearance).
*/
isDisabled?: boolean;

/**
* Loading state passed down from higher level used to trigger loader and text change.
*/
isLoading?: boolean;
} & ExtendedElement;

/**
* `import {Button} from "@chanzuckerberg/eds";`
Expand Down
48 changes: 47 additions & 1 deletion src/components/Link/Link-v2.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const Emphasis: StoryObj<Args> = {
},
};

export const LinkInParagraphContext: StoryObj<Args> = {
export const LinkInParagraphContext: StoryObj<ExtendArgs> = {
render: (
args: React.JSX.IntrinsicAttributes &
(LinkProps & React.RefAttributes<HTMLAnchorElement>),
Expand All @@ -86,3 +86,49 @@ export const LinkInParagraphContext: StoryObj<Args> = {
</div>
),
};

// Here, we introduce a special type extension to LinkProps, then use it in a
// composed component, to demonstrate the ability to offer custom props to a component
type ExtendArgs = LinkProps<{ to: string }>;
function ExtendedLink(args: ExtendArgs) {
return (
// eslint-disable-next-line no-alert
<Link {...args} onClick={() => alert(`handle to value: ${args.to}`)} />
);
}

/**
* You can extend a component's props for use with libraries that aid navigation, e.g., react-dom-router, et al.
*
* Steps to use:
*
* * import `LinkProps`
* * use the type param. to augment the types for `Link` with the libraries type, e.g., `type ExtendedProps = LinkProps<typeof CustomLink>;`
* * Now export a new function component that uses the new prop type and returns a composed function
*
* When using this pattern, you likely want to also specify the library's Link component using `as`
*
* ```tsx
* type ExtendedProps = LinkProps<typeof CustomLink>;
*
* export default function Link({children, ...other}: ExtendedProps) {
* return (
* <Link as={CustomLink} {...other}>
* {children}
* </Link>
* );
* }
* ```
*/
export const UsingExtendedLink: StoryObj<ExtendArgs> = {
render: (args) => (
<div>
Lorem ipsum dolor sit amet,{' '}
<ExtendedLink {...args} href="https://go.czi.team/eds" to="test">
consectetur adipiscing elit
</ExtendedLink>
. Morbi porta at ante quis molestie. Nam scelerisque id diam at iaculis.
Nullam sit amet iaculis erat. Nulla id tellus ante.{' '}
</div>
),
};
68 changes: 35 additions & 33 deletions src/components/Link/Link-v2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,42 @@ import Icon from '../Icon';

import styles from './Link-v2.module.css';

export type LinkProps = React.AnchorHTMLAttributes<HTMLAnchorElement> & {
// Component API
/**
* Component used to render the element. Meant to support interaction with framework navigation libraries.
*
* **Default is `"a"`**.
*/
as?: string | React.ElementType;
/**
* The link contents or label.
*/
children: string;
// Design API
/**
* Where `Link` sits alongside other text and content:
*
* * **inline** - Inline link inherits the text size established within the `<p>` paragraph they are embedded in.
* * **standalone** - Users can choose from the available sizes.
*/
context?: 'inline' | 'standalone';
/**
* (trailing) icon to use with the link
*/
icon?: Extract<IconName, 'chevron-right' | 'open-in-new'>;
/**
* Extra or lowered colors added to a link
*/
emphasis?: 'default' | 'high' | 'low';
export type LinkProps<ExtendedElement = unknown> =
React.AnchorHTMLAttributes<HTMLAnchorElement> & {
// Component API
/**
* Component used to render the element. Meant to support interaction with framework navigation libraries.
*
* **Default is `"a"`**.
*/
as?: string | React.ElementType;
/**
* The link contents or label.
*/
children: string;
// Design API
/**
* Where `Link` sits alongside other text and content:
*
* * **inline** - Inline link inherits the text size established within the `<p>` paragraph they are embedded in.
* * **standalone** - Users can choose from the available sizes.
*/
context?: 'inline' | 'standalone';
/**
* (trailing) icon to use with the link
*/
icon?: Extract<IconName, 'chevron-right' | 'open-in-new'>;
/**
* Extra or lowered colors added to a link
*/
emphasis?: 'default' | 'high' | 'low';

/**
* Link size inherits from the surrounding text.
*/
size?: Extract<Size, 'xs' | 'sm' | 'md' | 'lg' | 'xl'>;
};
/**
* Link size inherits from the surrounding text.
*/
size?: Extract<Size, 'xs' | 'sm' | 'md' | 'lg' | 'xl'>;
// };
} & ExtendedElement;

/**
* `import {Link} from "@chanzuckerberg/eds";`
Expand Down
Loading