@@ -68,19 +68,6 @@ export const parameters: Preview['parameters'] = {
},
backgrounds: {
values: [
- // TODO: remove the following three when the last 1.0 components have been migrated
- {
- name: 'eds-color-neutral-white',
- value: '#ffffff',
- },
- {
- name: 'eds-color-neutral-100',
- value: '#f4f6f8',
- },
- {
- name: 'eds-color-neutral-700',
- value: '#21272D',
- },
{
name: 'background-utility-default-high-emphasis',
value: '#0F2163',
diff --git a/src/components/Accordion/Accordion-v2.module.css b/src/components/Accordion/Accordion-v2.module.css
deleted file mode 100644
index 09d38152e..000000000
--- a/src/components/Accordion/Accordion-v2.module.css
+++ /dev/null
@@ -1,137 +0,0 @@
-@import '../../design-tokens/mixins.css';
-
-/*------------------------------------*\
- # ACCORDION
-\*------------------------------------*/
-/**
- * TODO: Icon inherits color from the surrounding text, but should use the matching -icon- tokens from below
- */
-
-/**
- * Accordion Panel that expands and collapses.
- * Reveals itself below the associated Accordion Button.
- */
- .accordion-panel {
- padding: 0 3.5rem 1rem 1rem;
-
- font: var(--eds-theme-typography-body-sm);
-
- &.accordion-panel--leading-icon {
- padding-left: 3.5rem;
- }
-
- color: var(--eds-theme-color-text-utility-default-primary);
-}
-
-/**
- * Accordion Button, wraps the heading and open indicator icon.
- */
-.accordion-button {
- display: flex;
- justify-content: flex-start;
- gap: 1rem;
- width: 100%;
-
- border: 0;
- text-align: left;
-
- border-radius: calc(var(--eds-theme-border-radius-objects-sm) * 1px);
-
- &:hover {
- background-color: var(--eds-theme-color-background-utility-default-no-emphasis-hover);
- }
-
- &:active {
- background-color: var(--eds-theme-color-background-utility-default-no-emphasis-active);
- }
-}
-
-.accordion-button--empty {
- pointer-events: none;
-}
-
-/**
- * Size variants
- */
-.accordion-button--sm,
-.accordion-button--md {
- padding: 0.5rem 1rem;
-}
-
-/**
- * Expand more (chevron) icon indicates open or closed status.
- *
- * This non-rotated icon points down and represents closed status.
- */
- .accordion-button__trailing-icon {
- flex: 0 0 content;
- transform: rotate(0);
-}
-
-/**
- * This rotated icon points up and represents open status.
- */
- .accordion-button__trailing-icon--open {
- transform: rotate(-180deg);
-}
-
-/**
- * Animates the icon rotation when opening and closing.
- */
- .accordion-button > .accordion-button__trailing-icon {
- transition: transform calc(var(--eds-anim-move-medium) * 1s) var(--eds-anim-ease);
-
- @media screen and (prefers-reduced-motion) {
- transition: none;
- }
-}
-
-.accordion-panel--hidden {
- padding: 0;
-}
-
-.accordion-button__heading {
- flex-grow: 2;
-
- & > * + * {
- display: block;
- }
-}
-
-.accordion-button:focus-visible {
- outline: 0.125rem solid var(--eds-theme-color-border-utility-focus)
-}
-
-.accordion-button__leading-icon {
- color: var(--eds-theme-color-icon-utility-default-secondary);
-
- /* Targeting NumberIcons and other images used in this specific context */
- & [role='img'] {
- display: inline-flex;
- }
-}
-
-.accordion-button__title {
- color: var(--eds-theme-color-text-utility-default-primary);
-}
-
-.accordion-button__subtitle {
- color: var(--eds-theme-color-text-utility-default-secondary);
-}
-
-
-.accordion-panel--md {
- font: var(--eds-theme-typography-body-md);
-}
-
-.accordion-panel--sm {
- font: var(--eds-theme-typography-body-sm);
-}
-
-/**
- * Accordion Row houses one Accordion Button subcomponent and its relevant Accordion Panel subcomponent.
- */
-.accordion-row {
- border-bottom: calc(var(--eds-border-width-sm) * 1px) solid
- var(--eds-theme-color-border-utility-default-low-emphasis);
-}
diff --git a/src/components/Accordion/Accordion-v2.stories.tsx b/src/components/Accordion/Accordion-v2.stories.tsx
deleted file mode 100644
index e43e0ca5d..000000000
--- a/src/components/Accordion/Accordion-v2.stories.tsx
+++ /dev/null
@@ -1,412 +0,0 @@
-import type { StoryObj, Meta } from '@storybook/react';
-import React from 'react';
-
-import { Accordion } from './Accordion-v2';
-import { chromaticViewports } from '../../util/viewports';
-import Icon from '../Icon';
-import { NumberIconV2 as NumberIcon } from '../NumberIcon';
-import Text from '../Text';
-
-type Args = React.ComponentProps;
-
-export default {
- title: 'Components/V2/Accordion',
- component: Accordion,
- parameters: {
- layout: 'centered',
- badges: ['intro-1.2', 'current-2.0'],
- },
- args: {
- headingAs: 'h2',
- className: 'w-96',
- children: (
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet,
- massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At
- tristique et ullamcorper rhoncus amet pharetra aliquet tortor.
- Suscipit dui, nunc sit dui tellus massa laoreet tellus.
-
-
- ),
- },
- argTypes: {
- children: {
- control: {
- type: null,
- },
- },
- },
- decorators: [(Story) =>
{Story()}
],
-} as Meta;
-
-type Story = StoryObj;
-
-export const Default: Story = {};
-
-export const TitleAndSubtitle: Story = {
- args: {
- ...Default.args,
- children: (
-
- "Quam lacus maecenas nibh malesuada."}
- title="Massa quam egestas massa."
- >
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet,
- massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At
- tristique et ullamcorper rhoncus amet pharetra aliquet tortor.
- Suscipit dui, nunc sit dui tellus massa laoreet tellus.
-
-
- ),
- },
-};
-
-export const HasLeadingIcon: Story = {
- args: {
- ...Default.args,
- children: (
-
-
- }
- subtitle="Quam lacus maecenas nibh malesuada."
- title="Massa quam egestas massa."
- >
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet,
- massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At
- tristique et ullamcorper rhoncus amet pharetra aliquet tortor.
- Suscipit dui, nunc sit dui tellus massa laoreet tellus.
-
-
- ),
- },
-};
-
-/**
- * You can add in `NumberIcon` or any other icon in the button for the accordion row.
- */
-export const HasLeadingNumberIcon: Story = {
- args: {
- ...Default.args,
- children: (
-
-
- }
- subtitle="Quam lacus maecenas nibh malesuada."
- title="Massa quam egestas massa."
- >
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet,
- massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At
- tristique et ullamcorper rhoncus amet pharetra aliquet tortor.
- Suscipit dui, nunc sit dui tellus massa laoreet tellus.
-
-
- ),
- },
-};
-
-/**
- * Default `Accordion` using the `small` size.
- */
-export const Small: Story = {
- args: {
- size: 'sm',
- },
-};
-
-/**
- * This demonstrates how one can combine multiple `Accordion` rows, where any of the rows can
- * be defaulted to open (using `defaultOpen`).
- */
-export const Stacked: Story = {
- args: {
- children: (
- <>
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet,
- massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At
- tristique et ullamcorper rhoncus amet pharetra aliquet tortor.
- Suscipit dui, nunc sit dui tellus massa laoreet tellus.
-
-
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet,
- massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At
- tristique et ullamcorper rhoncus amet pharetra aliquet tortor.
- Suscipit dui, nunc sit dui tellus massa laoreet tellus.
-
-
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet,
- massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At
- tristique et ullamcorper rhoncus amet pharetra aliquet tortor.
- Suscipit dui, nunc sit dui tellus massa laoreet tellus.
-
-
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet,
- massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At
- tristique et ullamcorper rhoncus amet pharetra aliquet tortor.
- Suscipit dui, nunc sit dui tellus massa laoreet tellus.
-
-
- >
- ),
- },
-};
-
-export const StackedSmall: Story = {
- args: {
- ...Stacked.args,
- size: 'sm',
- },
-};
-
-/**
- * This demonstrates how to specify that a section is not currently expandable using `isExpandable`.
- */
-export const EmptyStackedOpen: Story = {
- args: {
- children: (
- <>
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet,
- massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At
- tristique et ullamcorper rhoncus amet pharetra aliquet tortor.
- Suscipit dui, nunc sit dui tellus massa laoreet tellus.
-
-
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet,
- massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At
- tristique et ullamcorper rhoncus amet pharetra aliquet tortor.
- Suscipit dui, nunc sit dui tellus massa laoreet tellus.
-
-
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet,
- massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At
- tristique et ullamcorper rhoncus amet pharetra aliquet tortor.
- Suscipit dui, nunc sit dui tellus massa laoreet tellus.
-
-
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet,
- massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At
- tristique et ullamcorper rhoncus amet pharetra aliquet tortor.
- Suscipit dui, nunc sit dui tellus massa laoreet tellus.
-
-
- >
- ),
- },
-};
-
-export const StackedOpen: Story = {
- args: {
- children: (
- <>
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet,
- massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At
- tristique et ullamcorper rhoncus amet pharetra aliquet tortor.
- Suscipit dui, nunc sit dui tellus massa laoreet tellus.
-
-
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet,
- massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At
- tristique et ullamcorper rhoncus amet pharetra aliquet tortor.
- Suscipit dui, nunc sit dui tellus massa laoreet tellus.
-
-
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet,
- massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At
- tristique et ullamcorper rhoncus amet pharetra aliquet tortor.
- Suscipit dui, nunc sit dui tellus massa laoreet tellus.
-
-
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet,
- massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At
- tristique et ullamcorper rhoncus amet pharetra aliquet tortor.
- Suscipit dui, nunc sit dui tellus massa laoreet tellus.
-
-
- >
- ),
- },
-};
-
-export const StackedSmallOpen: Story = {
- args: {
- ...StackedOpen.args,
- size: 'sm',
- },
-};
-
-/**
- *
- * This shows how to use a render prop for the row, to allow controlling render based on component state.
- *
- * **NOTE**: Visual regression testing unhelpful since story value is in interaction and as a code example.
- */
-export const UsingRenderProp: Story = {
- render: () => (
-
-
- {({ open }) => (
- <>
-
- Accordion Button {(open && 'open') || 'closed'}
-
- Accordion Panel
- >
- )}
-
-
- ),
- parameters: {
- chromatic: { disableSnapshot: true },
- },
-};
-
-/**
- * Although headings should provide limited text, we allow for text to span multiple lines, preserving
- * the size of the state caret.
- */
-export const WithLargeHeader: Story = {
- parameters: {
- chromatic: {
- viewports: [chromaticViewports.ipadMini],
- },
- },
- args: {
- children: (
-
-
- Massa quam egestas massa. Massa quam egestas massa. Massa quam egestas
- massa. Massa quam egestas massa. Massa quam egestas massa.
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet,
- massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At
- tristique et ullamcorper rhoncus amet pharetra aliquet tortor.
- Suscipit dui, nunc sit dui tellus massa laoreet tellus.
-
-
- ),
- },
-};
-
-/**
- * You can use other EDS components within the `Accordion.Button` to allow for custom, non-text headers.
- *
- * **Example**: using `Text` and `Icon` in the `Accordion.Button`.
- */
-export const UsingComplexHeaders: Story = {
- parameters: {
- badges: ['intro-1.2', 'implementationExample', 'current-2.0'],
- },
- args: {
- children: (
- <>
-
-
-
-
- Step 1
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet,
- massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At
- tristique et ullamcorper rhoncus amet pharetra aliquet tortor.
- Suscipit dui, nunc sit dui tellus massa laoreet tellus.
-
-
-
-
-
-
- Step 2
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet,
- massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At
- tristique et ullamcorper rhoncus amet pharetra aliquet tortor.
- Suscipit dui, nunc sit dui tellus massa laoreet tellus.
-
-
- >
- ),
- },
-};
diff --git a/src/components/Accordion/Accordion-v2.test.tsx b/src/components/Accordion/Accordion-v2.test.tsx
deleted file mode 100644
index 4bc9444a2..000000000
--- a/src/components/Accordion/Accordion-v2.test.tsx
+++ /dev/null
@@ -1,115 +0,0 @@
-import { generateSnapshots } from '@chanzuckerberg/story-utils';
-import type { StoryFile } from '@storybook/testing-react';
-import { composeStories } from '@storybook/testing-react';
-import { render, screen } from '@testing-library/react';
-import userEvent from '@testing-library/user-event';
-import React from 'react';
-import { Accordion } from './Accordion-v2';
-import * as stories from './Accordion-v2.stories';
-
-const { Default } = composeStories(stories);
-
-describe('', () => {
- generateSnapshots(stories as StoryFile);
-
- it('should open and close Accordion panel clicking Accordion button', async () => {
- const user = userEvent.setup();
- render();
- expect(screen.queryByTestId('accordion-panel')).not.toBeInTheDocument();
- const accordionButton = screen.getByTestId('accordion-button');
-
- await user.click(accordionButton);
- expect(screen.getByTestId('accordion-panel')).toBeInTheDocument();
-
- await user.click(accordionButton);
- expect(screen.queryByTestId('accordion-panel')).not.toBeInTheDocument();
- });
-
- it('should open and close Accordion panel with space and enter keys on the Accordion button', async () => {
- const user = userEvent.setup();
- render();
- const accordionButton = screen.getByTestId('accordion-button');
- accordionButton.focus();
-
- await user.keyboard(' ');
- expect(screen.getByTestId('accordion-panel')).toBeInTheDocument();
-
- await user.keyboard(' ');
- expect(screen.queryByTestId('accordion-panel')).not.toBeInTheDocument();
-
- await user.keyboard('{enter}');
- expect(screen.getByTestId('accordion-panel')).toBeInTheDocument();
-
- await user.keyboard('{enter}');
- expect(screen.queryByTestId('accordion-panel')).not.toBeInTheDocument();
- });
-
- it('should call onClose callback when accordion closes', async () => {
- const user = userEvent.setup();
- const onClose = jest.fn();
- render(
-
-
-
- Accordion Button
-
- Accordion Panel
-
- ,
- );
- const accordionButton = screen.getByTestId('accordion-button');
-
- await user.click(accordionButton);
- expect(onClose).toHaveBeenCalledTimes(1);
- });
-
- it('should call onOpen callback when accordion opens', async () => {
- const user = userEvent.setup();
- const onClose = jest.fn();
- const onOpen = jest.fn();
- render(
-
-
-
- Accordion Button
-
- Accordion Panel
-
- ,
- );
- const accordionButton = screen.getByRole('button');
-
- await user.click(accordionButton);
- expect(onOpen).toHaveBeenCalledTimes(1);
- expect(onClose).not.toHaveBeenCalled();
- });
-
- it('should not call onOpen callback when accordion opens on an empty row', async () => {
- const user = userEvent.setup();
- const onClose = jest.fn();
- const onOpen = jest.fn();
- render(
-
-
-
- Accordion Button
-
- Accordion Panel
-
- ,
- );
- const accordionButton = screen.getByRole('button');
-
- await user.click(accordionButton);
- expect(onOpen).not.toHaveBeenCalled();
- expect(onClose).not.toHaveBeenCalled();
- });
-});
diff --git a/src/components/Accordion/Accordion-v2.tsx b/src/components/Accordion/Accordion-v2.tsx
deleted file mode 100644
index 54e82dde0..000000000
--- a/src/components/Accordion/Accordion-v2.tsx
+++ /dev/null
@@ -1,305 +0,0 @@
-import { Disclosure } from '@headlessui/react';
-import clsx from 'clsx';
-import React, { createContext, useContext } from 'react';
-import type { ReactNode } from 'react';
-import { ENTER_KEYCODE, SPACEBAR_KEYCODE } from '../../util/keycodes';
-import type { Size } from '../../util/variant-types';
-
-import Heading, { type HeadingElement } from '../Heading';
-import { type IconNameV2 as IconName, IconV2 as Icon } from '../Icon';
-import Text from '../Text';
-
-import styles from './Accordion-v2.module.css';
-
-type AccordionProps = {
- // Component API
- /**
- * Child node(s) that can be nested inside component.
- */
- children: ReactNode;
- /**
- * Additional classnames passed in for styling.
- */
- className?: string;
- // Design API
- /**
- * Used to specify which heading element should be rendered for each `Accordion.Title` child.
- *
- * **Default is `"h2"`**.
- */
- headingAs: HeadingElement;
- /**
- * Various sizes supported by the `Accordion`.
- *
- * **Default is `"md"`**.
- */
- size?: Extract;
-};
-
-type AccordionButtonProps = {
- // Component API
- /**
- * Child node(s) that can be nested inside component.
- */
- children?: ReactNode;
- /**
- * Additional classnames passed in for styling
- */
- className?: string;
- /**
- * Callback for when accordion is closed.
- */
- onClose?: () => void;
- /**
- * Callback for when according is opened.
- */
- onOpen?: () => void;
- // Design API
- /**
- * Used to specify which heading element should be rendered for the title.
- * If provided, overrides parent headingAs prop.
- */
- headingAs?: HeadingElement;
- /**
- * Icon to preceed the text in an accordion header
- */
- leadingIcon?: ReactNode;
- /**
- * Secondary text used to describe the content in more detail
- */
- subtitle?: ReactNode;
- /**
- * The title/heading of the component
- */
- title?: string;
- /**
- * Icon override for component's expand/collapse indicator.
- *
- * **Default is `"expand-more"`**.
- */
- trailingIcon?: Extract;
-};
-
-type AccordionPanelProps = {
- /**
- * Child node(s) that can be nested inside component.
- */
- children: ReactNode;
- /**
- * Additional class names passed in for styling
- */
- className?: string;
-};
-
-type AccordionRowProps = {
- /**
- * Child node(s) that can be nested inside component.
- */
- children: ReactNode | (({ open }: { open: boolean }) => ReactNode);
- /**
- * Additional class names passed in for styling.
- */
- className?: string;
- /**
- * Whether panel is expanded by default.
- */
- defaultOpen?: boolean;
- /**
- * Whether the row can show expandable content
- */
- isExpandable?: boolean;
- /**
- * Whether the row has a leading icon on the row's trigger
- */
- hasLeadingIcon?: boolean;
-};
-
-const AccordionContext = createContext<{
- headingAs: HeadingElement;
- size?: AccordionProps['size'];
-}>({
- headingAs: 'h2',
-});
-
-const AccordionRowContext = createContext<
- Pick
->({
- isExpandable: true,
- hasLeadingIcon: false,
-});
-
-/**
- * `import {Accordion} from "@chanzuckerberg/eds;`
- *
- * Displays one or more headers stacked on top of one another that can reveal or hide associated content.
- * This component is based on the [Disclosure](https://headlessui.com/react/disclosure) component, provided by HeadlessUI.
- *
- * @see https://headlessui.com/react/disclosure
- *
- */
-export const Accordion = ({
- children,
- className,
- headingAs,
- size = 'md',
- ...other
-}: AccordionProps) => {
- return (
-
-
],
} as Meta;
type Story = StoryObj;
export const Default: Story = {};
+export const TitleAndSubtitle: Story = {
+ args: {
+ ...Default.args,
+ children: (
+
+ "Quam lacus maecenas nibh malesuada."}
+ title="Massa quam egestas massa."
+ >
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet,
+ massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At
+ tristique et ullamcorper rhoncus amet pharetra aliquet tortor.
+ Suscipit dui, nunc sit dui tellus massa laoreet tellus.
+
+
+ ),
+ },
+};
+
+export const HasLeadingIcon: Story = {
+ args: {
+ ...Default.args,
+ children: (
+
+
+ }
+ subtitle="Quam lacus maecenas nibh malesuada."
+ title="Massa quam egestas massa."
+ >
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet,
+ massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At
+ tristique et ullamcorper rhoncus amet pharetra aliquet tortor.
+ Suscipit dui, nunc sit dui tellus massa laoreet tellus.
+
+
+ ),
+ },
+};
+
+/**
+ * You can add in `NumberIcon` or any other icon in the button for the accordion row.
+ */
+export const HasLeadingNumberIcon: Story = {
+ args: {
+ ...Default.args,
+ children: (
+
+
+ }
+ subtitle="Quam lacus maecenas nibh malesuada."
+ title="Massa quam egestas massa."
+ >
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet,
+ massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At
+ tristique et ullamcorper rhoncus amet pharetra aliquet tortor.
+ Suscipit dui, nunc sit dui tellus massa laoreet tellus.
+
+
+ ),
+ },
+};
+
/**
* Default `Accordion` using the `small` size.
*/
@@ -64,9 +143,10 @@ export const Stacked: Story = {
children: (
<>
-
- Massa quam egestas massa.
-
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet,
massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At
@@ -75,9 +155,10 @@ export const Stacked: Story = {
-
- Massa quam egestas massa.
-
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet,
massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At
@@ -86,9 +167,10 @@ export const Stacked: Story = {
-
- Massa quam egestas massa.
-
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet,
massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At
@@ -97,9 +179,10 @@ export const Stacked: Story = {
-
- Massa quam egestas massa.
-
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet,
massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At
@@ -119,21 +202,6 @@ export const StackedSmall: Story = {
},
};
-export const StackedOutline: Story = {
- args: {
- ...Stacked.args,
- hasOutline: true,
- },
-};
-
-export const StackedSmallOutline: Story = {
- args: {
- ...Stacked.args,
- size: 'sm',
- hasOutline: true,
- },
-};
-
/**
* This demonstrates how to specify that a section is not currently expandable using `isExpandable`.
*/
@@ -142,7 +210,7 @@ export const EmptyStackedOpen: Story = {
children: (
<>
- Massa quam egestas massa.
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet,
massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At
@@ -151,7 +219,7 @@ export const EmptyStackedOpen: Story = {
- Massa quam egestas massa.
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet,
massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At
@@ -160,7 +228,7 @@ export const EmptyStackedOpen: Story = {
- Massa quam egestas massa.
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet,
massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At
@@ -169,7 +237,7 @@ export const EmptyStackedOpen: Story = {
- Massa quam egestas massa.
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet,
massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At
@@ -187,7 +255,7 @@ export const StackedOpen: Story = {
children: (
<>
- Massa quam egestas massa.
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet,
massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At
@@ -196,7 +264,7 @@ export const StackedOpen: Story = {
- Massa quam egestas massa.
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet,
massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At
@@ -205,7 +273,7 @@ export const StackedOpen: Story = {
- Massa quam egestas massa.
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet,
massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At
@@ -214,7 +282,7 @@ export const StackedOpen: Story = {
- Massa quam egestas massa.
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet,
massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At
@@ -234,21 +302,6 @@ export const StackedSmallOpen: Story = {
},
};
-export const StackedOutlineOpen: Story = {
- args: {
- ...StackedOpen.args,
- hasOutline: true,
- },
-};
-
-export const StackedSmallOutlineOpen: Story = {
- args: {
- ...StackedOpen.args,
- size: 'sm',
- hasOutline: true,
- },
-};
-
/**
*
* This shows how to use a render prop for the row, to allow controlling render based on component state.
@@ -310,7 +363,7 @@ export const WithLargeHeader: Story = {
*/
export const UsingComplexHeaders: Story = {
parameters: {
- badges: ['intro-1.2', 'implementationExample'],
+ badges: ['intro-1.2', 'implementationExample', 'current-2.0'],
},
args: {
children: (
@@ -320,7 +373,7 @@ export const UsingComplexHeaders: Story = {
@@ -339,7 +392,7 @@ export const UsingComplexHeaders: Story = {
@@ -357,48 +410,3 @@ export const UsingComplexHeaders: Story = {
),
},
};
-
-/**
- * You can use other EDS components within the `Accordion.Button` to allow for custom, non-text headers.
- *
- * **Example**: using `Text` and `NumberIcon` in the `Accordion.Button`.
- */
-export const UsingNumberIconInHeaders: Story = {
- parameters: {
- badges: ['intro-1.2', 'implementationExample'],
- },
- args: {
- children: (
- <>
-
-
-
-
- Step 1
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet,
- massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At
- tristique et ullamcorper rhoncus amet pharetra aliquet tortor.
- Suscipit dui, nunc sit dui tellus massa laoreet tellus.
-
-
-
-
-
-
- Step 2
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet,
- massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At
- tristique et ullamcorper rhoncus amet pharetra aliquet tortor.
- Suscipit dui, nunc sit dui tellus massa laoreet tellus.
-
-
- >
- ),
- },
-};
diff --git a/src/components/Accordion/Accordion.tsx b/src/components/Accordion/Accordion.tsx
index c2f5d0900..9c7f10f57 100644
--- a/src/components/Accordion/Accordion.tsx
+++ b/src/components/Accordion/Accordion.tsx
@@ -4,12 +4,15 @@ import React, { createContext, useContext } from 'react';
import type { ReactNode } from 'react';
import { ENTER_KEYCODE, SPACEBAR_KEYCODE } from '../../util/keycodes';
import type { Size } from '../../util/variant-types';
-import Button from '../Button';
+
import Heading, { type HeadingElement } from '../Heading';
import Icon, { type IconName } from '../Icon';
+import Text from '../Text';
+
import styles from './Accordion.module.css';
type AccordionProps = {
+ // Component API
/**
* Child node(s) that can be nested inside component.
*/
@@ -18,12 +21,7 @@ type AccordionProps = {
* Additional classnames passed in for styling.
*/
className?: string;
- /**
- * Outline variant adds adjusts the `Accordion` style by defining a containing border and other layout adjustments.
- *
- * **Default is `false`**.
- */
- hasOutline?: boolean;
+ // Design API
/**
* Used to specify which heading element should be rendered for each `Accordion.Title` child.
*
@@ -39,33 +37,47 @@ type AccordionProps = {
};
type AccordionButtonProps = {
+ // Component API
/**
* Child node(s) that can be nested inside component.
*/
- children: ReactNode;
+ children?: ReactNode;
/**
* Additional classnames passed in for styling
*/
className?: string;
/**
- * Icon override for component.
- *
- * **Default is `"expand-more"`**.
+ * Callback for when accordion is closed.
*/
- icon?: Extract;
+ onClose?: () => void;
+ /**
+ * Callback for when according is opened.
+ */
+ onOpen?: () => void;
+ // Design API
/**
* Used to specify which heading element should be rendered for the title.
* If provided, overrides parent headingAs prop.
*/
headingAs?: HeadingElement;
/**
- * Callback for when accordion is closed.
+ * Icon to preceed the text in an accordion header
*/
- onClose?: () => void;
+ leadingIcon?: ReactNode;
/**
- * Callback for when according is opened.
+ * Secondary text used to describe the content in more detail
*/
- onOpen?: () => void;
+ subtitle?: ReactNode;
+ /**
+ * The title/heading of the component
+ */
+ title?: string;
+ /**
+ * Icon override for component's expand/collapse indicator.
+ *
+ * **Default is `"expand-more"`**.
+ */
+ trailingIcon?: Extract;
};
type AccordionPanelProps = {
@@ -74,7 +86,7 @@ type AccordionPanelProps = {
*/
children: ReactNode;
/**
- * Additional classnames passed in for styling
+ * Additional class names passed in for styling
*/
className?: string;
};
@@ -85,7 +97,7 @@ type AccordionRowProps = {
*/
children: ReactNode | (({ open }: { open: boolean }) => ReactNode);
/**
- * Additional classnames passed in for styling.
+ * Additional class names passed in for styling.
*/
className?: string;
/**
@@ -96,18 +108,24 @@ type AccordionRowProps = {
* Whether the row can show expandable content
*/
isExpandable?: boolean;
+ /**
+ * Whether the row has a leading icon on the row's trigger
+ */
+ hasLeadingIcon?: boolean;
};
const AccordionContext = createContext<{
headingAs: HeadingElement;
- hasOutline?: AccordionProps['hasOutline'];
size?: AccordionProps['size'];
}>({
headingAs: 'h2',
});
-const AccordionRowContext = createContext<{ isExpandable?: boolean }>({
+const AccordionRowContext = createContext<
+ Pick
+>({
isExpandable: true,
+ hasLeadingIcon: false,
});
/**
@@ -122,18 +140,13 @@ const AccordionRowContext = createContext<{ isExpandable?: boolean }>({
export const Accordion = ({
children,
className,
- hasOutline,
headingAs,
size = 'md',
...other
}: AccordionProps) => {
- const componentClassName = clsx(
- hasOutline && styles['accordion--outline'],
- className,
- );
return (
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus.
-
-
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus.
-
-
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus.
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus.
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus.
-
-
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus.
-
-
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus.
-
-
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus.
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus.
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus.
-
-
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus.
-
-
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus.
-
-
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus.
-
diff --git a/src/components/HorizontalStepper/HorizontalStepper.stories.tsx b/src/components/HorizontalStepper/HorizontalStepper.stories.tsx
index 6e16b5257..0b5f5089e 100644
--- a/src/components/HorizontalStepper/HorizontalStepper.stories.tsx
+++ b/src/components/HorizontalStepper/HorizontalStepper.stories.tsx
@@ -4,7 +4,6 @@ import React from 'react';
import { HorizontalStepper } from './HorizontalStepper';
import Button from '../Button';
import ButtonGroup from '../ButtonGroup';
-import Icon from '../Icon';
export default {
title: 'Components/HorizontalStepper',
@@ -18,12 +17,7 @@ export default {
badges: ['intro-1.0'],
},
- decorators: [
- (Story) => (
- // Pushes contents away from storybook borders.
-
{Story()}
- ),
- ],
+ decorators: [(Story) =>
{Story()}
],
} as Meta;
type Args = React.ComponentProps;
@@ -61,12 +55,16 @@ const InteractiveHorizontalStepper = ({ steps }: Args) => {
-
>
diff --git a/src/components/HorizontalStepper/HorizontalStepper.tsx b/src/components/HorizontalStepper/HorizontalStepper.tsx
index 7c14d4300..1f8746401 100644
--- a/src/components/HorizontalStepper/HorizontalStepper.tsx
+++ b/src/components/HorizontalStepper/HorizontalStepper.tsx
@@ -33,9 +33,9 @@ export type StepProps = {
*/
className?: string;
/**
- * Icon override for component. Default is 'check-circle'
+ * Icon override for component.'
*/
- icon?: Extract;
+ icon?: Extract;
/**
* Indicates which number the step is.
*/
@@ -150,7 +150,7 @@ export const HorizontalStepper = ({
*/
export const HorizontalStep = ({
className,
- icon = 'check-circle',
+ icon = 'checkmark-encircled-filled',
stepNumber,
text,
variant,
@@ -176,9 +176,9 @@ export const HorizontalStep = ({
);
diff --git a/src/components/HorizontalStepper/__snapshots__/HorizontalStepper.test.tsx.snap b/src/components/HorizontalStepper/__snapshots__/HorizontalStepper.test.tsx.snap
index 2c99456f5..cc65f7b08 100644
--- a/src/components/HorizontalStepper/__snapshots__/HorizontalStepper.test.tsx.snap
+++ b/src/components/HorizontalStepper/__snapshots__/HorizontalStepper.test.tsx.snap
@@ -12,8 +12,9 @@ exports[` CappedLine story renders snapshot 1`] = `
>
1
@@ -32,26 +33,11 @@ exports[` CappedLine story renders snapshot 1`] = `
>
-
-
- incomplete step 2 Step 2
-
-
-
+ 2
CappedLine story renders snapshot 1`] = `
>
-
-
- incomplete step 3 Step 3
-
-
-
+ 3
CappedLine story renders snapshot 1`] = `
>
-
-
- incomplete step 4 Step 4
-
-
-
+ 4
CappedLine story renders snapshot 1`] = `
>
-
-
- incomplete step 5 Step 5
-
-
-
+ 5
HorizontalSteps story renders snapshot 1`] = `
>
-
-
- incomplete step 1 Horizontal step
-
-
-
+ 1
HorizontalSteps story renders snapshot 1`] = `
>
1
@@ -425,8 +352,9 @@ exports[` HorizontalStepsDifferentNumbers story renders sna
>
1
@@ -441,8 +369,9 @@ exports[` HorizontalStepsDifferentNumbers story renders sna
>
2
@@ -457,8 +386,9 @@ exports[` HorizontalStepsDifferentNumbers story renders sna
>
3
@@ -473,8 +403,9 @@ exports[` HorizontalStepsDifferentNumbers story renders sna
>
4
@@ -489,8 +420,9 @@ exports[` HorizontalStepsDifferentNumbers story renders sna
>
5
@@ -505,8 +437,9 @@ exports[` HorizontalStepsDifferentNumbers story renders sna
>
6
@@ -521,8 +454,9 @@ exports[` HorizontalStepsDifferentNumbers story renders sna
>
7
@@ -537,8 +471,9 @@ exports[` HorizontalStepsDifferentNumbers story renders sna
>
8
@@ -553,8 +488,9 @@ exports[` HorizontalStepsDifferentNumbers story renders sna
>
9
@@ -569,8 +505,9 @@ exports[` HorizontalStepsDifferentNumbers story renders sna
>
10
@@ -585,8 +522,9 @@ exports[` HorizontalStepsDifferentNumbers story renders sna
>
21
@@ -601,8 +539,9 @@ exports[` HorizontalStepsDifferentNumbers story renders sna
>
32
@@ -617,8 +556,9 @@ exports[` HorizontalStepsDifferentNumbers story renders sna
>
43
@@ -633,8 +573,9 @@ exports[` HorizontalStepsDifferentNumbers story renders sna
>
54
@@ -649,8 +590,9 @@ exports[` HorizontalStepsDifferentNumbers story renders sna
>
65
@@ -676,8 +618,9 @@ exports[` OnFirstStep story renders snapshot 1`] = `
>
1
@@ -696,26 +639,11 @@ exports[` OnFirstStep story renders snapshot 1`] = `
>
-
-
- incomplete step 2 Step 2
-
-
-
+ 2
OnFirstStep story renders snapshot 1`] = `
>
-
-
- incomplete step 3 Step 3
-
-
-
+ 3
OnFirstStep story renders snapshot 1`] = `
>
-
-
- incomplete step 4 Step 4
-
-
-
+ 4
OnFirstStep story renders snapshot 1`] = `
>
-
-
- incomplete step 5 Step 5
-
-
-
+ 5
OnLastStep story renders snapshot 1`] = `
>
5
@@ -1054,8 +938,9 @@ exports[` SomeCompletedSteps story renders snapshot 1`] = `
>
3
@@ -1074,26 +959,11 @@ exports[` SomeCompletedSteps story renders snapshot 1`] = `
>
-
-
- incomplete step 4 Step 4
-
-
-
+ 4
SomeCompletedSteps story renders snapshot 1`] = `
>
-
-
- incomplete step 5 Step 5
-
-
-
+ 5
= {
- title: 'Components/V2/Icon',
- component: Icon,
- parameters: {
- layout: 'centered',
- badges: ['intro-1.0', 'current-2.0'],
- },
- argTypes: {
- name: {
- control: {
- type: 'select',
- },
- options: Object.keys(icons),
- },
- color: {
- control: {
- type: 'select',
- },
- // For now, take the variables and convert to equivalent tokens for the UI
- options: [
- 'currentColor',
- ...Object.keys(ColorTokens)
- .filter((tokenVarName) => tokenVarName.indexOf('Icon') !== -1)
- .map((tokenVarName) => `var(--${kebabCase(tokenVarName)})`),
- ],
- },
- },
-};
-
-export default meta;
-type Story = StoryObj;
-
-export const Default: Story = {
- render: ({ name, color, ...rest }) => {
- return ;
- },
- args: {
- name: 'close',
- purpose: 'decorative' as const,
- },
-};
-
-export const Medium: Story = {
- ...Default,
- args: {
- ...Default.args,
- size: '2em',
- },
-};
-
-export const Large: Story = {
- ...Default,
- args: {
- ...Default.args,
- size: '4em',
- },
-};
-
-/**
- * You can control the color of the icon using any valid CSS color values, including our token suite.
- *
- * If `currentColor` for the whole container isn't sufficient,
- * use a CSS variable in `color` with the token you need, or
- * style `fill` with Tailwind: https://tailwindcss.com/docs/fill
- */
-export const CustomColor: Story = {
- ...Default,
- args: {
- ...Default.args,
- color: 'var(--eds-theme-color-icon-utility-critical)',
- size: '2em',
- },
-};
-
-/**
- * Icons are positioned naturally in lines of text. Use the size, color, or other props
- * to match the recommended design and layout.
- *
- * See: https://material-ui.com/components/material-icons/
- */
-export const InText: Story = {
- render: (args) => {
- return (
-
- The svg icon defaults to the surrounding text size (
-
- ; 1em) by default.
-
- );
- },
-};
-
-/**
- * If your product needs icons not currently existing in the suite, you can introduce new
- * accessible icons by inserting the body of an SVG into `Icon`. Each resulting icon can be
- * treated like a standalone component, matching the recipe defined by design.
- */
-export const WithChildrenSvg: Story = {
- ...Default,
- args: {
- viewBox: '0 0 24 24',
- children: (
-
- ),
- },
-};
-
-const IconsInGrid = (args: IconProps) => (
-
-
- {(Object.keys(icons) as IconName[]).map((name) => {
- return (
-
-
- {name}
-
- );
- })}
-
-
-);
-
-/**
- * Grid of all the available icons. Use the controls to change color, or other attributes
- *
- * **NOTE**: some icons marked as deprecated will be removed in future releases.
- */
-export const IconGrid: Story = {
- render: (args) => ,
- parameters: {
- layout: 'padded',
- },
-};
diff --git a/src/components/Icon/Icon-v2.test.ts b/src/components/Icon/Icon-v2.test.ts
deleted file mode 100644
index af6f4f3b1..000000000
--- a/src/components/Icon/Icon-v2.test.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { generateSnapshots } from '@chanzuckerberg/story-utils';
-import type { StoryFile } from '@storybook/testing-react';
-import * as stories from './Icon-v2.stories';
-
-describe(' (v2)', () => {
- generateSnapshots(stories as StoryFile);
-});
diff --git a/src/components/Icon/Icon-v2.tsx b/src/components/Icon/Icon-v2.tsx
deleted file mode 100644
index 420e31a23..000000000
--- a/src/components/Icon/Icon-v2.tsx
+++ /dev/null
@@ -1,141 +0,0 @@
-import clsx from 'clsx';
-import type { ReactNode, CSSProperties } from 'react';
-import React from 'react';
-import icons, { type IconName } from '../../icons/spritemap-v2';
-import styles from './Icon-v2.module.css';
-
-export type { IconName } from '../../icons/spritemap-v2';
-
-// TODO: export union utility type of "Extract | (renderProps) => ReactNode" when updating IconName usages
-
-interface IconPropsBase {
- /**
- * CSS class names that can be appended to the component.
- */
- className?: string;
- /**
- * Either a fragment of svg elements (g, path, circle, etc.), or a single
- * svg element. Useful for creating specific icon components.
- *
- * @example
- * function CircleIcon(props: IconProps) {
- * return (
- *
- *
- *
- * )
- * }
- */
- children?: ReactNode;
- /**
- * The SVG Color, expects a valid css color (hex, rgb, css variable, etc.).
- *
- */
- color?: string;
- /**
- * ID used so the svg can read the title of the SVG icon to the user when accessibility is needed
- */
- id?: string;
- /**
- * Name of icon to reference in icon sprite
- */
- name?: IconName;
- /**
- * Width/Height string (px, rem, em, vh, etc.)
- * Generally prefer using "em" as it sizes to its parent container.
- */
- size?: string;
- /**
- * viewBox for the svg, used when the svg information is passed via children.
- * To match included icons, recommend view box of "0 0 24 24"
- */
- viewBox?: string;
-}
-
-interface InformativeIconProps extends IconPropsBase {
- /**
- * The role of the icon.
- *
- * Use "informative" when the icon **_does_** provide additional meaning to other text on the
- * page. You'll be required to pass in a title to label the icon.
- */
- purpose: 'informative';
- title: string;
-}
-
-interface DecorativeIconProps extends IconPropsBase {
- /**
- * The role of the icon.
- *
- * Use "decorative" when the icon **_does not_** provide any additional context or meaning to
- * associated text. Basically the icon is for show and people don't need it to understand what's
- * on the page.
- */
- purpose: 'decorative';
-}
-
-export type IconProps = DecorativeIconProps | InformativeIconProps;
-
-interface SvgStyle extends CSSProperties {
- '--icon-size'?: string;
-}
-
-/**
- * `import {Icon} from "@chanzuckerberg/eds";`
- *
- * Render arbitrary SVG path data while enforcing good accessibility practices.
- *
- * Icons are based on [Material Rounded](https://fonts.google.com/icons?icon.set=Material+Icons&icon.style=Rounded),
- * and are encoded in a spritemap in `src/icons`.
- */
-export const Icon = (props: IconProps) => {
- const {
- children,
- className,
- color = 'currentColor',
- name,
- id,
- purpose,
- size,
- viewBox,
- } = props;
-
- const componentClassName = clsx(styles['icon'], className);
- const style: SvgStyle = {
- '--icon-size': size,
- };
-
- const svgCommonProps = {
- className: componentClassName,
- fill: color,
- height: size,
- /**
- * height/width html properties are overriden by the defaults applied in CSS module
- */
- style,
- width: size,
- xmlns: 'http://www.w3.org/2000/svg',
- viewBox: name ? icons[name].viewBox : viewBox,
- };
-
- // allow passing custom SVGs to render, otherwise
- // load from the spritemap of EDS icons
- const computedSvg = name ? icons[name].content : children;
-
- if (purpose === 'informative') {
- return (
-
- {props.title}
- {computedSvg}
-
- );
- } else {
- return (
-
- {computedSvg}
-
- );
- }
-};
-
-Icon.displayName = 'Icon';
diff --git a/src/components/Icon/Icon.module.css b/src/components/Icon/Icon.module.css
index 6e91c55bd..8db0ecf7a 100755
--- a/src/components/Icon/Icon.module.css
+++ b/src/components/Icon/Icon.module.css
@@ -18,10 +18,3 @@
width: var(--icon-size, var(--icon-size-default, 1em));
height: var(--icon-size, var(--icon-size-default, 1em));
}
-
-/**
- * A block icon fills 100% of the width of its container
- */
-.icon--full-width {
- display: block;
-}
diff --git a/src/components/Icon/Icon.stories.tsx b/src/components/Icon/Icon.stories.tsx
index 9845ce834..61f77cdc7 100644
--- a/src/components/Icon/Icon.stories.tsx
+++ b/src/components/Icon/Icon.stories.tsx
@@ -2,9 +2,10 @@ import type { StoryObj, Meta } from '@storybook/react';
import kebabCase from 'lodash/kebabCase';
import React from 'react';
import { Icon, type IconProps } from './Icon';
-import icons, { type IconName } from '../../icons/spritemap';
+import icons, { type IconName } from '../../icons/spritemap-v2';
import * as ColorTokens from '../../tokens-dist/ts/colors';
import Text from '../Text';
+
import styles from './Icon.stories.module.css';
const meta: Meta = {
@@ -12,7 +13,7 @@ const meta: Meta = {
component: Icon,
parameters: {
layout: 'centered',
- badges: ['intro-1.0'],
+ badges: ['intro-1.0', 'current-2.0'],
},
argTypes: {
name: {
@@ -28,9 +29,9 @@ const meta: Meta = {
// For now, take the variables and convert to equivalent tokens for the UI
options: [
'currentColor',
- ...Object.keys(ColorTokens).map(
- (tokenVarName) => `var(--${kebabCase(tokenVarName)})`,
- ),
+ ...Object.keys(ColorTokens)
+ .filter((tokenVarName) => tokenVarName.indexOf('Icon') !== -1)
+ .map((tokenVarName) => `var(--${kebabCase(tokenVarName)})`),
],
},
},
@@ -76,7 +77,7 @@ export const CustomColor: Story = {
...Default,
args: {
...Default.args,
- color: 'var(--eds-color-brand-grape-400)',
+ color: 'var(--eds-theme-color-icon-utility-critical)',
size: '2em',
},
};
@@ -94,7 +95,7 @@ export const InText: Story = {
The svg icon defaults to the surrounding text size (
@@ -127,62 +128,6 @@ const IconsInGrid = (args: IconProps) => (
{name}
- {name === 'avatar' && (
-
-
- This has been replaced by person. This will be deprecated
-
- )}
- {name === 'class-copy' && (
-
-
- This has been replaced by book. This will be deprecated
-
- )}
- {name === 'file-copy' && (
-
-
- This has been replaced by copy. This will be deprecated
-
- )}
- {name === 'more-vert' && (
-
-
- This has been replaced by dots-vertical. This will be deprecated
-
- ),
+export const LongText: StoryObj = {
+ args: {
+ title:
+ 'Long text inline notification. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.',
+ },
};
diff --git a/src/components/InlineNotification/InlineNotification.tsx b/src/components/InlineNotification/InlineNotification.tsx
index dbe19df68..ad02ab3aa 100644
--- a/src/components/InlineNotification/InlineNotification.tsx
+++ b/src/components/InlineNotification/InlineNotification.tsx
@@ -1,102 +1,75 @@
import clsx from 'clsx';
import React from 'react';
-import type { ReactNode } from 'react';
-import Icon, { type IconName } from '../Icon';
-import Text from '../Text';
-import styles from './InlineNotification.module.css';
-export const VARIANTS = ['brand', 'success', 'warning'] as const;
+import getIconNameFromStatus from '../../util/getIconNameFromStatus-v2';
+import type { Status } from '../../util/variant-types';
-const variantToIconAssetsMap: {
- [key in Variant]: {
- icon: Extract<
- IconName,
- 'info' | 'check-circle' | 'warning' | 'error-inverted'
- >;
- title: 'info' | 'success' | 'alert';
- };
-} = {
- brand: {
- icon: 'info',
- title: 'info',
- },
- success: {
- icon: 'check-circle',
- title: 'success',
- },
- warning: {
- icon: 'warning',
- title: 'alert',
- },
-};
+import Icon from '../Icon';
+import Text from '../Text';
-type Variant = (typeof VARIANTS)[number];
+import styles from './InlineNotification.module.css';
-type Props = {
+type InlineNotificationProps = {
+ // Component API
/**
* CSS class names that can be appended to the component for styling.
*/
className?: string;
+ // Design API
/**
- * Toggles notification that fills the full width of its container.
+ * Keyword to characterize the state of the notification
*/
- isFullWidth?: boolean;
+ status?: Status;
/**
- * Toggles the stronger background variants.
+ * Secondary text used to describe the content in more detail
*/
- isStrong?: boolean;
+ subTitle?: React.ReactNode;
/**
- * The text contents of the tag, nested inside the component, in addition to the icon.
+ * The title/heading of the component
*/
- text: ReactNode;
- /**
- * The color variant of the tag.
- */
- variant: Variant;
+ title: string;
};
/**
* `import {InlineNotification} from "@chanzuckerberg/eds";`
*
- * This component provides an inline banner accompanied with an icon for messaging users.
+ * An alert placed within a section of a page to provide a contextual notification. For example, an error which applies to multiple fields within a form.
*/
export const InlineNotification = ({
className,
- isFullWidth,
- isStrong,
- text,
- variant,
+ status = 'informational',
+ subTitle,
+ title,
...other
-}: Props) => {
- const subtle = !isStrong;
+}: InlineNotificationProps) => {
const componentClassName = clsx(
styles['inline-notification'],
- styles[`inline-notification--${variant}`],
- subtle && styles['inline-notification--subtle'],
- isFullWidth && styles[`inline-notification--full-width`],
- isFullWidth && subtle && styles[`inline-notification--full-width-subtle`],
+ status && styles[`inline-notification--status-${status}`],
className,
);
- const iconClassName = clsx(
- styles['inline-notification__icon'],
- styles[`inline-notification__icon--${variant}`],
- );
-
- const textClassName = clsx(styles[`inline-notification__text`]);
-
return (
- Long text inline notification. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
-
- Additional text which provides additional detail
-
-
-
-`;
diff --git a/src/components/InlineNotification/__snapshots__/InlineNotification.test.ts.snap b/src/components/InlineNotification/__snapshots__/InlineNotification.test.ts.snap
index c54fef784..4e04543da 100644
--- a/src/components/InlineNotification/__snapshots__/InlineNotification.test.ts.snap
+++ b/src/components/InlineNotification/__snapshots__/InlineNotification.test.ts.snap
@@ -1,457 +1,241 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[` FullWidthLongText story renders snapshot 1`] = `
+exports[` Critical story renders snapshot 1`] = `
-
- success
-
-
- Long text inline notification. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
-
-
- warning inline notification lorem ipsum
-
+ Additional text which provides additional detail
+
`;
exports[` LongText story renders snapshot 1`] = `
-
- success
-
-
- Long text inline notification. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
-
+
+ Long text inline notification. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
+
+
`;
-exports[` StrongVariants story renders snapshot 1`] = `
+exports[` Warning story renders snapshot 1`] = `
-
-
-
- info
-
-
-
-
- brand inline notification lorem ipsum
-
-
+
+
+ Additional text
+
+ which provides additional detail
+
+
`;
-exports[` Success story renders snapshot 1`] = `
+exports[` WithSubTitle story renders snapshot 1`] = `
-
- success
-
-
- Inline notifications lorem ipsum text
-
+
+ Inline notifications lorem ipsum text
+
+
+ Additional text which provides additional detail
+
+
`;
diff --git a/src/components/InlineNotification/index.ts b/src/components/InlineNotification/index.ts
index 4587a66e8..62e79f80a 100644
--- a/src/components/InlineNotification/index.ts
+++ b/src/components/InlineNotification/index.ts
@@ -1,2 +1 @@
export { InlineNotification as default } from './InlineNotification';
-export { InlineNotification as InlineNotificationV2 } from './InlineNotification-v2';
diff --git a/src/components/Input/Input-v2.module.css b/src/components/Input/Input-v2.module.css
deleted file mode 100644
index fba2becb9..000000000
--- a/src/components/Input/Input-v2.module.css
+++ /dev/null
@@ -1,12 +0,0 @@
-@import '../../design-tokens/mixins.css';
-
-/*------------------------------------*\
- # INPUT
-\*------------------------------------*/
-
-/**
- * Default input styles
- */
-.input {
- @mixin inputStylesV2;
-}
diff --git a/src/components/Input/Input-v2.tsx b/src/components/Input/Input-v2.tsx
deleted file mode 100644
index 6e7c0a27d..000000000
--- a/src/components/Input/Input-v2.tsx
+++ /dev/null
@@ -1,113 +0,0 @@
-import clsx from 'clsx';
-import type { ChangeEventHandler } from 'react';
-import React, { forwardRef } from 'react';
-import type { Status } from '../../util/variant-types';
-import styles from './Input-v2.module.css';
-
-export type InputProps = React.InputHTMLAttributes & {
- /**
- * Aria-label to provide an accesible name for the text input if no visible label is provided.
- */
- 'aria-label'?: string;
- /**
- * CSS class names that can be appended to the component.
- */
- className?: string;
- /**
- * Disables the input and prevents editing the contents
- */
- disabled?: boolean;
- /**
- * HTML id for the component
- */
- id?: string;
- /**
- * Gives a hint as to the type of data needed for text input
- */
- inputMode?:
- | 'text'
- | 'email'
- | 'url'
- | 'search'
- | 'tel'
- | 'none'
- | 'numeric'
- | 'decimal';
- /**
- * Maximum number the input can take.
- */
- max?: number | string;
- /**
- * Minimum number the input can take.
- */
- min?: number | string;
- /**
- * HTML name attribute for the input
- */
- name?: string;
- /**
- * Function that fires when field value has changed
- */
- onChange?: ChangeEventHandler;
- /**
- * Placeholder attribute for input. Note: placeholder should be used sparingly
- */
- placeholder?: string;
- /**
- * Toggles the form control's interactivity. When `readOnly` is set to `true`, the form control is not interactive
- */
- readOnly?: boolean;
- /**
- * Indicates that field is required for form to be successfully submitted
- */
- required?: boolean;
- /**
- * Title attribute on input
- */
- title?: string;
- /**
- * HTML type attribute, allowing switching between text, password, and other HTML5 input field types
- */
- type?: React.HTMLInputTypeAttribute;
- /**
- * The value of the input
- */
- value?: string | number;
- /**
- * The default value of the input
- */
- defaultValue?: string | number;
- // Design API
- /**
- * Status for the field state
- *
- * **Default is `"default"`**.
- */
- status?: 'default' | Extract;
-};
-
-/**
- * Input component for one line of text.
- */
-export const Input = forwardRef(
- ({ className, disabled, id, status, ...other }, ref) => {
- const componentClassName = clsx(
- styles['input'],
- status === 'critical' && styles['error'],
- status === 'warning' && styles['warning'],
- className,
- );
-
- return (
-
- );
- },
-);
-
-Input.displayName = 'Input';
diff --git a/src/components/Input/Input.module.css b/src/components/Input/Input.module.css
index e9d7288a1..fba2becb9 100644
--- a/src/components/Input/Input.module.css
+++ b/src/components/Input/Input.module.css
@@ -8,6 +8,5 @@
* Default input styles
*/
.input {
- @mixin inputStyles;
- padding-left: calc(var(--eds-size-2) / 16 * 1rem);
+ @mixin inputStylesV2;
}
diff --git a/src/components/Input/Input.tsx b/src/components/Input/Input.tsx
index f5a23be01..dee6d4b48 100644
--- a/src/components/Input/Input.tsx
+++ b/src/components/Input/Input.tsx
@@ -1,6 +1,7 @@
import clsx from 'clsx';
import type { ChangeEventHandler } from 'react';
import React, { forwardRef } from 'react';
+import type { Status } from '../../util/variant-types';
import styles from './Input.module.css';
export type InputProps = React.InputHTMLAttributes & {
@@ -32,10 +33,6 @@ export type InputProps = React.InputHTMLAttributes & {
| 'none'
| 'numeric'
| 'decimal';
- /**
- * Error state of the form field
- */
- isError?: boolean;
/**
* Maximum number the input can take.
*/
@@ -80,16 +77,24 @@ export type InputProps = React.InputHTMLAttributes & {
* The default value of the input
*/
defaultValue?: string | number;
+ // Design API
+ /**
+ * Status for the field state
+ *
+ * **Default is `"default"`**.
+ */
+ status?: 'default' | Extract;
};
/**
* Input component for one line of text.
*/
export const Input = forwardRef(
- ({ className, disabled, id, isError, ...other }, ref) => {
+ ({ className, disabled, id, status, ...other }, ref) => {
const componentClassName = clsx(
styles['input'],
- isError && styles['error'],
+ status === 'critical' && styles['error'],
+ status === 'warning' && styles['warning'],
className,
);
diff --git a/src/components/Input/index.ts b/src/components/Input/index.ts
index 81e5d2cfd..78585db2e 100644
--- a/src/components/Input/index.ts
+++ b/src/components/Input/index.ts
@@ -1,3 +1,2 @@
export { Input as default } from './Input';
export type { InputProps } from './Input';
-export { Input as InputV2, type InputProps as InputPropsV2 } from './Input-v2';
diff --git a/src/components/InputField/InputField-v2.module.css b/src/components/InputField/InputField-v2.module.css
deleted file mode 100644
index 0912eaeb2..000000000
--- a/src/components/InputField/InputField-v2.module.css
+++ /dev/null
@@ -1,105 +0,0 @@
-/*------------------------------------*\
- # INPUT FIELD
-\*------------------------------------*/
-/**
- * TODO: Icon inherits color from the surrounding text, but should use the matching -icon- tokens from below
- */
-
-/**
- * Wraps the Label and the optional/required hint.
- * TODO: match/share the overline styles between Select and InputField
- */
-.input-field__overline {
- display: flex;
- margin-bottom: 0.25rem;
- gap: 0.25rem;
-}
-
-.input-field__overline--no-label {
- justify-content: flex-start;
-}
-
-/**
- * Input Field Within
- *
- * A slot to put arbitrary content that appears within the input field border to the right.
- *
- * Typically used for buttons and icon buttons to enable things like show/hide password buttons .
- */
-.input-field__input-within {
- position: absolute;
- right: 0.5rem;
- top: 0;
- bottom: 0;
- display: grid;
- align-items: center;
- justify-content: center;
-}
-
-.input-field__leading-icon {
- position: absolute;
- pointer-events: none;
- top: 0;
- bottom: 0;
- left: 0.5rem;
- display: grid;
- align-items: center;
- justify-content: center;
-
- color: var(--eds-theme-color-icon-utility-default-secondary);
-}
-
-/**
- * Input field body
- */
-.input-field__body {
- position: relative;
-}
-
-.input-field__input--leading-icon {
- padding-left: 2.25rem;
-}
-
-.input-field__input--input-within {
- padding-right: 8rem;
-}
-
-.input-field__footer {
- display: flex;
- justify-content: space-between;
-}
-
-.input-field--has-fieldNote {
- margin-bottom: 0.25rem;
-}
-
-.input-field__label {
- color: var(--eds-theme-color-text-utility-default-primary);
- font: var(--eds-theme-typography-form-label);
-}
-
-.input-field__label--disabled {
- color: var(--eds-theme-color-text-utility-disabled-primary);
-}
-
-.input-field__required-text {
- color: var(--eds-theme-color-text-utility-default-secondary);
- font: var(--eds-theme-typography-body-sm);
-}
-
-.input-field__character-counter {
- font: var(--eds-theme-typography-body-sm);
- text-align: right;
- flex: 1 0 50%;
-
- color: var(--eds-theme-color-text-utility-default-secondary);
-}
-
-.input-field--invalid-length {
- color: var(--eds-theme-color-text-utility-critical);
-}
-
-.input-field__required-text--disabled {
- color: var(--eds-theme-color-text-utility-disabled-primary);
-}
-
diff --git a/src/components/InputField/InputField-v2.stories.tsx b/src/components/InputField/InputField-v2.stories.tsx
deleted file mode 100644
index 6cd19a5c9..000000000
--- a/src/components/InputField/InputField-v2.stories.tsx
+++ /dev/null
@@ -1,282 +0,0 @@
-import type { StoryObj, Meta } from '@storybook/react';
-import React from 'react';
-
-import { InputField } from './InputField-v2';
-import { ButtonV2 as Button } from '../Button';
-import { Label } from '../Label/Label';
-import { Table } from '../Table/Table';
-
-const meta: Meta = {
- title: 'Components/V2/InputField',
- component: InputField,
- parameters: {
- layout: 'centered',
- badges: ['intro-1.0', 'current-2.0'],
- backgrounds: {
- default: 'eds-color-neutral-white',
- },
- },
- args: {
- className: 'w-96',
- },
- decorators: [(Story) =>
{Story()}
],
-};
-
-export default meta;
-type Story = StoryObj;
-
-export const Default: Story = {
- args: {
- label: 'Default input field',
- fieldNote: 'This is a fieldnote.',
- },
-};
-
-/**
- * Fields, when containing text, have a theme matching the rest of the interface.
- */
-export const WithText: Story = {
- args: {
- label: 'Default input field',
- fieldNote: 'This is a fieldnote.',
- defaultValue: 'Text value',
- },
-};
-
-/**
- * Fields do not required a `fieldNote`.
- */
-export const NoFieldnote: Story = {
- args: {
- label: 'Default input field',
- },
-};
-
-/**
- * Fields can have an error state.
- */
-export const Error: Story = {
- args: {
- label: 'Error input field',
- status: 'critical',
- fieldNote: 'This is a fieldnote with an error.',
- },
-};
-
-/**
- * Fields can have a warning state.
- */
-export const Warning: Story = {
- args: {
- label: 'Warning input field',
- status: 'warning',
- fieldNote:
- 'This uses the warning treatment and also applies to the field note',
- },
-};
-
-/**
- * Read-only fields can have a value and are not editable, but are different from disabled fields.
- */
-export const ReadOnly: Story = {
- args: {
- label: 'Read-only field',
- readOnly: true,
- defaultValue: 'some read-only information',
- fieldNote: 'This will show up like text, but not be interactive',
- },
-};
-
-/**
- * Fields can be marked as disabled (and contain a value in such cases).
- */
-export const Disabled: Story = {
- args: {
- label: 'Disabled input field',
- disabled: true,
- fieldNote: 'This InputField is disabled',
- defaultValue: 'Text in disabled field',
- },
-};
-
-/**
- * Fields can have a leading icon, indicating what kind of content can go into the field.
- */
-export const LeadingIcon: Story = {
- args: {
- leadingIcon: 'search',
- 'aria-label': 'search field',
- placeholder: 'Search...',
- },
-};
-
-/**
- * Fields can be marked as required.
- */
-export const Required: Story = {
- args: {
- label: 'Input field with fieldNote',
- showHint: true,
- required: true,
- fieldNote: 'This is a fieldnote for a required input field.',
- },
-};
-
-/**
- * Fields can be marked as required.
- */
-export const RequiredDisabled: Story = {
- args: {
- label: 'Input field with fieldNote',
- showHint: true,
- required: true,
- disabled: true,
- fieldNote: 'This is a fieldnote for a required input field.',
- },
-};
-
-/**
- * When not using a visible label with `InputField`, you must apply some time of ARIA label to the component, like `aria-label`.
- */
-export const NoVisibleLabel: Story = {
- args: {
- 'aria-label': 'Input for no visible label',
- fieldNote: 'This input field has no visible label',
- required: true,
- },
-};
-
-/**
- * Password fields show dots instead of characters, to help with security.
- */
-export const Password: Story = {
- args: {
- label: 'Password',
- type: 'password',
- },
-};
-
-/**
- * Fields can have an optional field hint added, for extra clarity.
- */
-export const ShowHint: Story = {
- args: {
- label: 'Field with Optional Hint',
- showHint: true,
- },
-};
-
-/**
- * You can render certain components **within** an `InputField`, such as a button, icon, or other
- * small component. This facility is used to implement controls that should appear visibly nested
- * within the button, to the right-hand side.
- *
- * Please keep the text of the button brief (button width < 128px)
- */
-export const InputWithin: Story = {
- parameters: {
- chromatic: { disableSnapshot: true },
- docs: {
- source: {
- type: 'dynamic',
- },
- },
- },
- render: () => (
-
- Button
-
- }
- label="Input field with button inside"
- type="text"
- />
- ),
-};
-
-/**
- * You can lock the maximum length of the text content of `InputField`. When setting `maxLength`,
- * the field will reuse the browser's [textarea](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea)
- * behavior (e.g., prevent further text from being typed, prevent keydown events, etc.).
- */
-export const WithAMaxLength: Story = {
- args: {
- defaultValue: 'Some initial text',
- label: 'test label',
- maxLength: 15,
- required: true,
- },
- render: (args) => ,
-};
-
-/**
- * If you want to signal that a field has reached a maximum length but want to allow more text to be typed, you can use
- * `recommendedMaxLength`. This will show a similar UI to using `maxLength` but will allow more text to be typed, and
- * emit any appropriate events.
- */
-export const WithARecommendedLength: Story = {
- args: {
- defaultValue: 'Some initial text',
- label: 'Shortened Length Field',
- recommendedMaxLength: 15,
- required: true,
- },
- render: (args) => ,
-};
-
-/**
- * Both `maxLength` and `recommendedMaxLength` can be specified at the same time. Text length between `recommendedMaxLength`
- * and `maxLength` will show the treatment warning the user about the text length being violated.
- */
-export const WithBothMaxAndRecommendedLength: Story = {
- args: {
- label: 'test label',
- defaultValue: 'Some initial text',
- fieldNote: 'Longer Field Description',
- maxLength: 20,
- recommendedMaxLength: 15,
- required: true,
- },
- render: (args) => ,
-};
-
-/**
- * This **implementation example** shows how to attach labels to fields and labels
- * when the label is in a different column.
- */
-export const TabularInput: Story = {
- parameters: {
- badges: ['intro-1.1', 'implementationExample'],
- },
- render: (args) => (
-
-
-
- Label
- Field
-
-
-
-
-
-
-
-
-
-
-
-
-
- ),
-};
diff --git a/src/components/InputField/InputField-v2.test.tsx b/src/components/InputField/InputField-v2.test.tsx
deleted file mode 100644
index 994d1e6f5..000000000
--- a/src/components/InputField/InputField-v2.test.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-import { generateSnapshots } from '@chanzuckerberg/story-utils';
-import type { StoryFile } from '@storybook/testing-react';
-import { render, screen } from '@testing-library/react';
-
-import userEvent from '@testing-library/user-event';
-import React from 'react';
-import { InputField } from './InputField-v2';
-
-import * as stories from './InputField-v2.stories';
-
-describe('', () => {
- generateSnapshots(stories as StoryFile);
-
- it('handles changes to the text within the component', async () => {
- const user = userEvent.setup();
- const onChange = jest.fn();
-
- render(
- ,
- );
- const input = screen.getByTestId('test-input');
- const testText = 'typing';
-
- input.focus();
-
- await user.keyboard(testText);
-
- expect(onChange).toHaveBeenCalledTimes(testText.length);
- });
-
- it('will not fire when maxLength is reached', async () => {
- const user = userEvent.setup();
- const onChange = jest.fn();
- const testText = 'typing';
-
- render(
- ,
- );
- const input = screen.getByTestId('test-input');
-
- input.focus();
- await user.keyboard(testText);
-
- expect(onChange).toHaveBeenCalledTimes(0);
- });
-
- it('will fire when recommendedMaxLength is reached', async () => {
- const user = userEvent.setup();
- const onChange = jest.fn();
- const testText = 'typing';
-
- render(
- ,
- );
- const input = screen.getByTestId('test-input');
-
- input.focus();
- await user.keyboard(testText);
-
- expect(onChange).toHaveBeenCalledTimes(testText.length);
- });
-});
diff --git a/src/components/InputField/InputField-v2.tsx b/src/components/InputField/InputField-v2.tsx
deleted file mode 100644
index 3342e9dfc..000000000
--- a/src/components/InputField/InputField-v2.tsx
+++ /dev/null
@@ -1,342 +0,0 @@
-import clsx from 'clsx';
-import type { ChangeEventHandler, ReactNode } from 'react';
-import React, { forwardRef, useState } from 'react';
-import { getMinValue } from '../../util/getMinValue';
-import { useId } from '../../util/useId';
-import type {
- EitherInclusive,
- ForwardedRefComponent,
-} from '../../util/utility-types';
-import type { Status } from '../../util/variant-types';
-import FieldLabel from '../FieldLabel';
-import { FieldNoteV2 as FieldNote } from '../FieldNote';
-import { IconV2 as Icon, type IconNameV2 as IconName } from '../Icon';
-import { InputV2 as Input } from '../Input';
-import Text from '../Text';
-import styles from './InputField-v2.module.css';
-
-export type InputFieldProps = React.InputHTMLAttributes & {
- // Component API
- /**
- * CSS class names that can be appended to the component.
- */
- className?: string;
- /**
- * Default value passed down from higher levels for initial state
- */
- defaultValue?: string | number;
- /**
- * Disables the field and prevents editing the contents
- */
- disabled?: boolean;
- /**
- * HTML id for the component
- */
- id?: string;
- /**
- * Gives a hint as to the type of data needed for text input (e.g., to render the correct input keyboard on mobile).
- */
- inputMode?: React.InputHTMLAttributes['inputMode'];
- /**
- * Node(s) that can be nested within the input field (e.g., secondary and tertiary `Button` components)
- */
- inputWithin?: ReactNode;
- /**
- * Maximum value allowed for the input, if type is 'number'.
- */
- max?: number | string;
- /**
- * Minimum value allowed for the input, if type is 'number'.
- */
- min?: number | string;
- /**
- * HTML name attribute for the input
- */
- name?: string;
- /**
- * Function that runs on change of the input
- */
- onChange?: ChangeEventHandler;
- /**
- * Toggles the form control's interactivity. When `readOnly` is set to `true`, the form control is not interactive
- */
- readOnly?: boolean;
- /**
- * Title attribute on input
- */
- title?: string;
- /**
- * HTML type attribute, allowing switching between text, password, and other HTML5 input field types
- *
- * **NOTE**: this excludes types that correspond to non-text controls, like `checkbox`, `radio`, etc.
- */
- type?: Extract<
- React.InputHTMLAttributes['type'],
- | 'text'
- | 'password'
- | 'datetime'
- | 'datetime-local'
- | 'date'
- | 'month'
- | 'time'
- | 'week'
- | 'number'
- | 'email'
- | 'url'
- | 'search'
- | 'tel'
- >;
- /**
- * Value passed down from higher levels for initial state
- */
- value?: string | number;
- // Design API
- /**
- * Text under the text input used to provide a description or error message to describe the input.
- */
- fieldNote?: ReactNode;
- /**
- * An icon that prefixes the field input.
- */
- leadingIcon?: IconName;
- /**
- * Placeholder attribute for input. Note: placeholder should be used sparingly
- */
- placeholder?: string;
- /**
- * Behaves similar to `maxLength` but allows the user to continue typing more text.
- * Should not be larger than `maxLength`, if present.
- */
- recommendedMaxLength?: number;
- /**
- * Indicates that field is required for form to be successfully submitted
- */
- required?: boolean;
- /**
- * Whether it should show the field hint or not
- *
- * **Default is `"false"`**.
- */
- showHint?: boolean;
- /**
- * Status for the field state
- *
- * **Default is `"default"`**.
- */
- status?: 'default' | Extract;
-} & EitherInclusive<
- {
- /**
- * Visible text label for the component.
- */
- label: string;
- },
- {
- /**
- * Aria-label to provide an accesible name for the text input if no visible label is provided.
- */
- 'aria-label': string;
- }
- >;
-
-type InputFieldType = ForwardedRefComponent<
- HTMLInputElement,
- InputFieldProps
-> & {
- Input?: typeof Input;
- Label?: typeof FieldLabel;
-};
-
-/**
- * `import {InputField} from "@chanzuckerberg/eds";`
- *
- * An input with optional labels and error messaging built-in.
- *
- * **NOTE**: This component requires `label` or `aria-label` prop
- */
-export const InputField: InputFieldType = forwardRef(
- (
- {
- 'aria-describedby': ariaDescribedBy,
- className,
- disabled,
- fieldNote,
- id,
- inputWithin,
- label,
- leadingIcon,
- maxLength,
- onChange,
- readOnly,
- recommendedMaxLength,
- required,
- showHint,
- status = 'default',
- type = 'text',
- ...other
- },
- ref,
- ) => {
- const shouldRenderOverline = !!(label || required);
- const [fieldText, setFieldText] = useState(other.defaultValue);
-
- const overlineClassName = clsx(
- styles['input-field__overline'],
- !label && styles['input-field__overline--no-label'],
- );
-
- const labelClassName = clsx(
- styles['input-field__label'],
- disabled && styles['input-field__label--disabled'],
- );
-
- const requiredTextClassName = clsx(
- styles['input-field__required-text'],
- disabled && styles['input-field__required-text--disabled'],
- );
-
- const inputBodyClassName = clsx(
- styles['input-field__body'],
- fieldNote && styles['input-field--has-fieldNote'],
- );
-
- const fieldLength = fieldText?.toString().length ?? 0;
-
- const textExceedsMaxLength =
- maxLength !== undefined && fieldLength ? fieldLength > maxLength : false;
-
- const textExceedsRecommendedLength =
- recommendedMaxLength !== undefined && fieldLength
- ? fieldLength > recommendedMaxLength
- : false;
-
- const shouldRenderError =
- textExceedsMaxLength || textExceedsRecommendedLength;
-
- const generatedIdVar = useId();
- const idVar = id || generatedIdVar;
-
- const generatedAriaDescribedById = useId();
- const ariaDescribedByVar = fieldNote
- ? ariaDescribedBy || generatedAriaDescribedById
- : undefined;
-
- const fieldLengthCountClassName = clsx(
- (textExceedsMaxLength || textExceedsRecommendedLength) &&
- styles['input-field--invalid-length'],
- );
-
- // Modify the padding of `Input` to account for trailing/leading icons and trailing buttons
- const inputOverlayClassName = clsx(
- leadingIcon && styles['input-field__input--leading-icon'],
- inputWithin && styles['input-field__input--input-within'],
- );
- // Pick the smallest of the lengths to set as the maximum value allowed
- const maxLengthShown = getMinValue(maxLength, recommendedMaxLength);
-
- return (
-
],
};
export default meta;
@@ -29,42 +32,112 @@ export const Default: Story = {
},
};
+/**
+ * Fields, when containing text, have a theme matching the rest of the interface.
+ */
+export const WithText: Story = {
+ args: {
+ label: 'Default input field',
+ fieldNote: 'This is a fieldnote.',
+ defaultValue: 'Text value',
+ },
+};
+
+/**
+ * Fields do not required a `fieldNote`.
+ */
export const NoFieldnote: Story = {
args: {
label: 'Default input field',
},
};
+/**
+ * Fields can have an error state.
+ */
export const Error: Story = {
args: {
label: 'Error input field',
- isError: true,
+ status: 'critical',
fieldNote: 'This is a fieldnote with an error.',
},
};
+/**
+ * Fields can have a warning state.
+ */
+export const Warning: Story = {
+ args: {
+ label: 'Warning input field',
+ status: 'warning',
+ fieldNote:
+ 'This uses the warning treatment and also applies to the field note',
+ },
+};
+
+/**
+ * Read-only fields can have a value and are not editable, but are different from disabled fields.
+ */
+export const ReadOnly: Story = {
+ args: {
+ label: 'Read-only field',
+ readOnly: true,
+ defaultValue: 'some read-only information',
+ fieldNote: 'This will show up like text, but not be interactive',
+ },
+};
+
+/**
+ * Fields can be marked as disabled (and contain a value in such cases).
+ */
export const Disabled: Story = {
args: {
label: 'Disabled input field',
disabled: true,
fieldNote: 'This InputField is disabled',
+ defaultValue: 'Text in disabled field',
},
- parameters: {
- axe: {
- // Disabled input does not need to meet color contrast
- disabledRules: ['color-contrast'],
- },
+};
+
+/**
+ * Fields can have a leading icon, indicating what kind of content can go into the field.
+ */
+export const LeadingIcon: Story = {
+ args: {
+ leadingIcon: 'search',
+ 'aria-label': 'search field',
+ placeholder: 'Search...',
},
};
+/**
+ * Fields can be marked as required.
+ */
export const Required: Story = {
args: {
label: 'Input field with fieldNote',
+ showHint: true,
+ required: true,
+ fieldNote: 'This is a fieldnote for a required input field.',
+ },
+};
+
+/**
+ * Fields can be marked as required.
+ */
+export const RequiredDisabled: Story = {
+ args: {
+ label: 'Input field with fieldNote',
+ showHint: true,
required: true,
+ disabled: true,
fieldNote: 'This is a fieldnote for a required input field.',
},
};
+/**
+ * When not using a visible label with `InputField`, you must apply some time of ARIA label to the component, like `aria-label`.
+ */
export const NoVisibleLabel: Story = {
args: {
'aria-label': 'Input for no visible label',
@@ -73,10 +146,32 @@ export const NoVisibleLabel: Story = {
},
};
+/**
+ * Password fields show dots instead of characters, to help with security.
+ */
+export const Password: Story = {
+ args: {
+ label: 'Password',
+ type: 'password',
+ },
+};
+
+/**
+ * Fields can have an optional field hint added, for extra clarity.
+ */
+export const ShowHint: Story = {
+ args: {
+ label: 'Field with Optional Hint',
+ showHint: true,
+ },
+};
+
/**
* You can render certain components **within** an `InputField`, such as a button, icon, or other
* small component. This facility is used to implement controls that should appear visibly nested
* within the button, to the right-hand side.
+ *
+ * Please keep the text of the button brief (button width < 128px)
*/
export const InputWithin: Story = {
parameters: {
@@ -90,7 +185,7 @@ export const InputWithin: Story = {
render: () => (
+
Button
}
@@ -123,7 +218,7 @@ export const WithAMaxLength: Story = {
export const WithARecommendedLength: Story = {
args: {
defaultValue: 'Some initial text',
- label: 'test label',
+ label: 'Shortened Length Field',
recommendedMaxLength: 15,
required: true,
},
diff --git a/src/components/InputField/InputField.tsx b/src/components/InputField/InputField.tsx
index 406ec1bfe..d63f1fcf3 100644
--- a/src/components/InputField/InputField.tsx
+++ b/src/components/InputField/InputField.tsx
@@ -7,25 +7,28 @@ import type {
EitherInclusive,
ForwardedRefComponent,
} from '../../util/utility-types';
+import type { Status } from '../../util/variant-types';
+import FieldLabel from '../FieldLabel';
import FieldNote from '../FieldNote';
+import Icon, { type IconName } from '../Icon';
import Input from '../Input';
-import InputLabel from '../InputLabel';
import Text from '../Text';
import styles from './InputField.module.css';
export type InputFieldProps = React.InputHTMLAttributes & {
+ // Component API
/**
* CSS class names that can be appended to the component.
*/
className?: string;
/**
- * Disables the field and prevents editing the contents
+ * Default value passed down from higher levels for initial state
*/
- disabled?: boolean;
+ defaultValue?: string | number;
/**
- * Text under the text input used to provide a description or error message to describe the input.
+ * Disables the field and prevents editing the contents
*/
- fieldNote?: ReactNode;
+ disabled?: boolean;
/**
* HTML id for the component
*/
@@ -35,15 +38,9 @@ export type InputFieldProps = React.InputHTMLAttributes & {
*/
inputMode?: React.InputHTMLAttributes['inputMode'];
/**
- * Node(s) that can be nested within the input field
+ * Node(s) that can be nested within the input field (e.g., secondary and tertiary `Button` components)
*/
inputWithin?: ReactNode;
- /**
- * Error variant of the form field
- *
- * **Default is `false`**.
- */
- isError?: boolean;
/**
* Maximum value allowed for the input, if type is 'number'.
*/
@@ -60,23 +57,10 @@ export type InputFieldProps = React.InputHTMLAttributes & {
* Function that runs on change of the input
*/
onChange?: ChangeEventHandler;
- /**
- * Placeholder attribute for input. Note: placeholder should be used sparingly
- */
- placeholder?: string;
/**
* Toggles the form control's interactivity. When `readOnly` is set to `true`, the form control is not interactive
*/
readOnly?: boolean;
- /**
- * Behaves similar to `maxLength` but allows the user to continue typing more text.
- * Should not be larger than `maxLength`, if present.
- */
- recommendedMaxLength?: number;
- /**
- * Indicates that field is required for form to be successfully submitted
- */
- required?: boolean;
/**
* Title attribute on input
*/
@@ -106,10 +90,40 @@ export type InputFieldProps = React.InputHTMLAttributes & {
* Value passed down from higher levels for initial state
*/
value?: string | number;
+ // Design API
/**
- * Default value passed down from higher levels for initial state
+ * Text under the text input used to provide a description or error message to describe the input.
*/
- defaultValue?: string | number;
+ fieldNote?: ReactNode;
+ /**
+ * An icon that prefixes the field input.
+ */
+ leadingIcon?: IconName;
+ /**
+ * Placeholder attribute for input. Note: placeholder should be used sparingly
+ */
+ placeholder?: string;
+ /**
+ * Behaves similar to `maxLength` but allows the user to continue typing more text.
+ * Should not be larger than `maxLength`, if present.
+ */
+ recommendedMaxLength?: number;
+ /**
+ * Indicates that field is required for form to be successfully submitted
+ */
+ required?: boolean;
+ /**
+ * Whether it should show the field hint or not
+ *
+ * **Default is `"false"`**.
+ */
+ showHint?: boolean;
+ /**
+ * Status for the field state
+ *
+ * **Default is `"default"`**.
+ */
+ status?: 'default' | Extract;
} & EitherInclusive<
{
/**
@@ -130,7 +144,7 @@ type InputFieldType = ForwardedRefComponent<
InputFieldProps
> & {
Input?: typeof Input;
- Label?: typeof InputLabel;
+ Label?: typeof FieldLabel;
};
/**
@@ -149,12 +163,15 @@ export const InputField: InputFieldType = forwardRef(
fieldNote,
id,
inputWithin,
- isError,
label,
+ leadingIcon,
maxLength,
onChange,
+ readOnly,
recommendedMaxLength,
required,
+ showHint,
+ status = 'default',
type = 'text',
...other
},
@@ -194,7 +211,7 @@ export const InputField: InputFieldType = forwardRef(
: false;
const shouldRenderError =
- isError || textExceedsMaxLength || textExceedsRecommendedLength;
+ textExceedsMaxLength || textExceedsRecommendedLength;
const generatedIdVar = useId();
const idVar = id || generatedIdVar;
@@ -209,6 +226,11 @@ export const InputField: InputFieldType = forwardRef(
styles['input-field--invalid-length'],
);
+ // Modify the padding of `Input` to account for trailing/leading icons and trailing buttons
+ const inputOverlayClassName = clsx(
+ leadingIcon && styles['input-field__input--leading-icon'],
+ inputWithin && styles['input-field__input--input-within'],
+ );
// Pick the smallest of the lengths to set as the maximum value allowed
const maxLengthShown = getMinValue(maxLength, recommendedMaxLength);
@@ -217,36 +239,59 @@ export const InputField: InputFieldType = forwardRef(
{shouldRenderOverline && (
- Lorem ipsum dolor sit amet,{' '}
-
- consectetur adipiscing elit
-
- . Morbi porta at ante quis molestie. Nam scelerisque id diam at iaculis.
- Nullam sit amet iaculis erat. Nulla id tellus ante.{' '}
-
- Aliquam pellentesque ipsum sagittis, commodo neque at, ornare est.
- Maecenas a malesuada sem, vitae euismod erat. Nullam molestie nunc non
- dui dignissim fermentum.
- {' '}
- Aliquam id volutpat nulla, sed auctor orci. Fusce cursus leo nisi. Fusce
- vehicula vitae nisl nec ultricies. Cras ut enim nec magna semper egestas.
- Sed sed quam id nisl bibendum convallis. Proin suscipit, odio{' '}
-
- vel pulvinar
- {' '}
- euismod, risus eros ullamcorper lectus, non blandit nulla dui eget massa.
-
- ),
-};
-
-/**
- * Links will inherit the color from the surrounding text color definition (default emphasis, inline links)
- */
-export const InheritColor: StoryObj = {
- args: {
- children: 'Inheriting',
- },
- render: (args) => (
-
- Lorem ipsum dolor sit amet,{' '}
-
- consectetur adipiscing elit
-
- . Morbi porta at ante quis molestie. Nam scelerisque id diam at iaculis.
- Nullam sit amet iaculis erat. Nulla id tellus ante.{' '}
-
- Aliquam pellentesque ipsum sagittis, commodo neque at, ornare est.
- Maecenas a malesuada sem, vitae euismod erat. Nullam molestie nunc non
- dui dignissim fermentum.
- {' '}
- Aliquam id volutpat nulla, sed auctor orci. Fusce cursus leo nisi. Fusce
- vehicula vitae nisl nec ultricies. Cras ut enim nec magna semper egestas.
- Sed sed quam id nisl bibendum convallis. Proin suscipit, odio{' '}
-
- vel pulvinar
- {' '}
- euismod, risus eros ullamcorper lectus, non blandit nulla dui eget massa.
-
- ),
- decorators: [
- (Story) =>
{Story()}
,
- ],
-};
-
-/**
- * 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 = LinkV2Props<{ to: string }>;
-function ExtendedLink(args: ExtendArgs) {
- return (
- // eslint-disable-next-line no-alert
- 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;`
- * * 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;
- *
- * export default function Link({children, ...other}: ExtendedProps) {
- * return (
- *
- * {children}
- *
- * );
- * }
- * ```
- */
-export const UsingExtendedLink: StoryObj = {
- render: (args) => (
-
- Lorem ipsum dolor sit amet,{' '}
-
- consectetur adipiscing elit
-
- . Morbi porta at ante quis molestie. Nam scelerisque id diam at iaculis.
- Nullam sit amet iaculis erat. Nulla id tellus ante.{' '}
-
- ),
-};
diff --git a/src/components/Link/Link-v2.test.tsx b/src/components/Link/Link-v2.test.tsx
deleted file mode 100644
index eed27a220..000000000
--- a/src/components/Link/Link-v2.test.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import { generateSnapshots } from '@chanzuckerberg/story-utils';
-import type { StoryFile } from '@storybook/testing-react';
-import { render, screen } from '@testing-library/react';
-import userEvent from '@testing-library/user-event';
-import React from 'react';
-import { Link } from './Link-v2';
-import * as stories from './Link-v2.stories';
-
-describe('', () => {
- generateSnapshots(stories as StoryFile);
-
- it('renders the text in the link', () => {
- render(Click);
-
- expect(screen.getByRole('link')).toHaveTextContent('Click');
- });
-
- it('fires callback on click', async () => {
- const user = userEvent.setup();
- const onClick = jest.fn();
- render(
-
- Click
- ,
- );
-
- await user.click(screen.getByRole('link'));
- expect(onClick).toHaveBeenCalled();
- });
-
- it('passes test ids down properly', () => {
- render(
-
- Click
- ,
- );
- expect(screen.getByTestId('example-test-id')).toMatchSnapshot();
- });
-
- it('passes class names down properly', () => {
- render(
-
- Click
- ,
- );
- expect(screen.getByTestId('example-class-name')).toMatchSnapshot();
- });
-
- it('forwards refs', () => {
- const ref = React.createRef();
- render(
-
- Click
- ,
- );
-
- ref.current!.focus();
-
- const link = screen.getByRole('link');
- expect(link).toHaveFocus();
- });
-});
diff --git a/src/components/Link/Link-v2.tsx b/src/components/Link/Link-v2.tsx
deleted file mode 100644
index d78f52506..000000000
--- a/src/components/Link/Link-v2.tsx
+++ /dev/null
@@ -1,105 +0,0 @@
-import clsx from 'clsx';
-import React, { forwardRef } from 'react';
-import type { Size } from '../../util/variant-types';
-import { IconV2 as Icon, type IconNameV2 as IconName } from '../Icon';
-
-import styles from './Link-v2.module.css';
-
-export type LinkV2Props =
- React.AnchorHTMLAttributes & {
- // 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. Using ReactNode to support customized text treatments
- */
- children: React.ReactNode;
- // Design API
- /**
- * Where `Link` sits alongside other text and content:
- *
- * * **inline** - Inline link inherits the text size established within the `
- Lorem ipsum dolor sit amet,
-
-
- consectetur adipiscing elit
-
- . Morbi porta at ante quis molestie. Nam scelerisque id diam at iaculis. Nullam sit amet iaculis erat. Nulla id tellus ante.
-
-
-
-`;
-
-exports[` passes class names down properly 1`] = `
-
- Click
-
-`;
-
-exports[` passes test ids down properly 1`] = `
-
- Click
-
-`;
diff --git a/src/components/Link/__snapshots__/Link.test.tsx.snap b/src/components/Link/__snapshots__/Link.test.tsx.snap
index e2fb7c818..372e29a80 100644
--- a/src/components/Link/__snapshots__/Link.test.tsx.snap
+++ b/src/components/Link/__snapshots__/Link.test.tsx.snap
@@ -1,81 +1,244 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[` Default story renders snapshot 1`] = `
-
- Link
-
-`;
-
-exports[` LinkInParagraphContext story renders snapshot 1`] = `
-
- Lorem ipsum dolor sit amet,
-
- consectetur adipiscing elit
+ Link
- . Morbi porta at ante quis molestie. Nam scelerisque id diam at iaculis. Nullam sit amet iaculis erat. Nulla id tellus ante.
-
+
`;
-exports[` LinkRightIcon story renders snapshot 1`] = `
- UsingExtendedLink story renders snapshot 1`] = `
+
- Link
-
-
- opens in a new tab
-
-
-
-
+
+ Lorem ipsum dolor sit amet,
+
+
+ consectetur adipiscing elit
+
+ . Morbi porta at ante quis molestie. Nam scelerisque id diam at iaculis. Nullam sit amet iaculis erat. Nulla id tellus ante.
+
+
`;
diff --git a/src/components/Menu/index.ts b/src/components/Menu/index.ts
index adddd5db0..5577958b0 100644
--- a/src/components/Menu/index.ts
+++ b/src/components/Menu/index.ts
@@ -1,2 +1 @@
export { Menu as default } from './Menu';
-export { Menu as MenuV2 } from './Menu-v2';
diff --git a/src/components/Modal/Modal-v2.module.css b/src/components/Modal/Modal-v2.module.css
deleted file mode 100755
index 1700a56de..000000000
--- a/src/components/Modal/Modal-v2.module.css
+++ /dev/null
@@ -1,230 +0,0 @@
-@import '../../design-tokens/mixins.css';
-
-/*------------------------------------*\
- # MODAL
-\*------------------------------------*/
-/**
- * TODO: Icon inherits color from the surrounding text, but should use the matching -icon- tokens from below
- */
-
-/**
- * The modal wrapper and overlay which takes up the entire screen.
- */
-.modal,
-.modal__overlay {
- position: fixed;
- top: 0;
- left: 0;
- height: 100vh;
- width: 100%;
-}
-
-/**
- * The inverted background of the modal to provide contrast with the actual modal.
- */
-.modal__overlay {
- &.modal__overlay--emphasis-high {
- background-color: rgb(from var(--eds-theme-color-background-utility-overlay-high-emphasis) r g b / 0.8);
- }
-
- &.modal__overlay--emphasis-low {
- background-color: rgb(from var(--eds-theme-color-background-utility-overlay-low-emphasis) r g b / 0.5);
- }
-}
-
-/**
- * The modal container which positions the modal in the center of the screen.
- */
-.modal {
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 1rem;
-
- /**
- * Ensures modal is above other components. This is not a design token for now since we need to align on
- * z-indeces across the system
- */
- z-index: 1050;
-}
-
-/**
- * Modal transition animations.
- */
-.modal__transition--enter {
- transition: opacity calc(var(--eds-anim-fade-long) * 1s) var(--eds-anim-ease);
- @media (prefers-reduced-motion) {
- transition: none;
- }
-}
-
-.modal__transition--enterFrom {
- opacity: 0;
-}
-
-.modal__transition--enterTo {
- opacity: 1;
-}
-
-.modal__transition--leave {
- transition: opacity calc(var(--eds-anim-fade-long) * 1s) var(--eds-anim-ease);
- @media (prefers-reduced-motion) {
- transition: none;
- }
-}
-
-.modal__transition--leaveFrom {
- opacity: 1;
-}
-
-.modal__transition--leaveTo {
- opacity: 0;
-}
-
-/**
- * The content of the modal, which can wrap header, body, and footer.
- */
-.modal__content {
- position: relative;
- overflow: hidden;
-
- /**
- * This transparent border is for Window High Contrast Mode, which removes all
- * background colors but makes borders 100% opacity black. Without this, the
- * modal would have no clear boundary.
- */
- border: var(--eds-theme-form-border-width) transparent var(--eds-theme-color-background-utility-container);
-
- display: flex;
- flex-direction: column;
-
- background-color: var(--eds-theme-color-background-utility-container);
-}
-
-/**
- * The large modal size used for the lg/default modal.
- */
-.modal__content--lg {
- @media all and (min-width: $eds-bp-xs) {
- width: 100%;
- height: 100%;
- }
-
- @media all and (min-width: $eds-bp-sm) {
- max-height: 40rem;
- max-width: 50rem;
- }
-
- @media all and (min-width: $eds-bp-md) {
- max-height: 40rem;
- max-width: 50rem;
- }
-
- @media all and (min-width: $eds-bp-lg) {
- max-height: 40rem;
- max-width: 60rem;
- }
-
- @media all and (min-width: $eds-bp-xl) {
- max-height: 40rem;
- max-width: 60rem;
- --modal-horizontal-padding: 4rem;
- }
-}
-
-/**
- * The small modal size used for the modal.
- */
- .modal__content--sm {
- @media all and (min-width: $eds-bp-xs) {
- max-height: 30rem;
- max-width: 35rem;
- }
-
- @media all and (min-width: $eds-bp-sm) {
- max-height: 30rem;
- max-width: 25rem;
- }
-
- @media all and (min-width: $eds-bp-md) {
- max-height: 30rem;
- max-width: 25rem;
- }
-
- @media all and (min-width: $eds-bp-xl) {
- max-height: 30rem;
- max-width: 35rem;
- --modal-horizontal-padding: 4rem;
- }
-}
-
-
-/**
- * Allows scrolling of the modal content except for sticky footer.
- * This functionality is our intended scroll behavior but consuming teams can
- * style the body content as they wish to handle overflow in various ways.
- */
-.modal__content--scrollable {
- overflow: auto;
-}
-
-/**
- * The modal close button.
- */
-.modal__close-button {
- position: absolute;
- top: 0;
- right: 0;
-}
-
-/*------------------------------------*\
- # MODAL BODY
-\*------------------------------------*/
-
-/**
- * The body of the modal
- */
- .modal-body {
- flex: 1;
- padding: 0 2rem;
-}
-
-/*------------------------------------*\
- # MODAL FOOTER
-\*------------------------------------*/
-
-/**
- * Footer for the modal.
- */
- .modal-footer {
- width: 100%;
- z-index: 1000;
-
- padding: 1.5rem 2rem;
-
- background-color: var(--eds-theme-color-background-utility-container);
-}
-
-.modal-footer--sticky {
- position: sticky;
- bottom: 0;
-
- box-shadow: var(--eds-box-shadow-xl);
-}
-
-/*------------------------------------*\
- # MODAL HEADER
-\*------------------------------------*/
-
-/**
- * Header for the modal.
- */
- .modal-header {
- width: 100%;
-
- padding: 1.5rem 2rem;
-}
-
-.modal-sub-title {
- color: var(--eds-theme-color-text-utility-default-secondary);
-}
\ No newline at end of file
diff --git a/src/components/Modal/Modal-v2.stories.tsx b/src/components/Modal/Modal-v2.stories.tsx
deleted file mode 100644
index 03b57ace9..000000000
--- a/src/components/Modal/Modal-v2.stories.tsx
+++ /dev/null
@@ -1,477 +0,0 @@
-import type { StoryObj, Meta } from '@storybook/react';
-import React from 'react';
-import { useState } from 'react';
-
-import { Modal, ModalContent } from './Modal-v2';
-import { Heading, Text } from '../../';
-import { chromaticViewports, storybookViewports } from '../../util/viewports';
-import { ButtonV2 as Button } from '../Button';
-import { ButtonGroupV2 as ButtonGroup } from '../ButtonGroup';
-
-export default {
- title: 'Components/V2/Modal',
- component: Modal,
- parameters: {
- // The modal is initially closed for most of these stories,
- // which renders testing it for visual regressions unhelpful.
- chromatic: { disableSnapshot: true },
- badges: ['intro-1.0', 'current-2.0'],
- },
- tags: ['autodocs'],
- argTypes: {
- // For some reason, storybook is not able to pick up the doc.s automatically. Adding manually.
- children: {
- control: {
- type: null,
- },
- description:
- 'Contains the sub-components for a Modal, including `.Header` , `.Title` , `.Body` , `.Footer` , `.Stepper`',
- },
- open: {
- type: 'boolean',
- description: 'Whether or not the modal is visible.',
- },
- hideCloseButton: {
- description:
- 'Hides the close button in the top right of the modal. **Default is `false`**.',
- type: 'boolean',
- },
- isScrollable: {
- description:
- 'Toggles scrollable variant of the modal. If modal is scrollable, footer is not, and vice versa.',
- type: 'boolean',
- },
- size: {
- description: 'Max size of the modal, which responds to the viewport',
- control: {
- type: 'select',
- },
- options: ['sm', 'lg'],
- defaultValue: 'lg',
- },
- },
- decorators: [(Story) =>
{Story()}
],
-} as Meta;
-
-type Args = React.ComponentProps;
-type Story = StoryObj;
-type InteractiveArgs = Omit;
-
-/**
- * Wrapper for triggering `Modal` with a button
- * @param args
- * @returns
- */
-function InteractiveExample(args: InteractiveArgs) {
- const [open, setOpen] = useState(false);
-
- return (
- <>
- setOpen(true)} rank="primary">
- Open the modal
-
-
- setOpen(false)} open={open} />
- >
- );
-}
-
-/**
- * Clicking on a trigger item (in this case `Button`) will cause a modal to open.
- *
- * **Note**: this only works from certain screens in Storybook. If it doesn't work as expected, view from the
- * "docs" sub-page.
- */
-export const Default: StoryObj = {
- render: (args) => (
-
-
- Modal Title
- Modal Sub-title
-
-
-
Modal Content
-
-
-
- {}} rank="primary">
- Primary Action
-
- {}} rank="secondary">
- Secondary
-
-
-
-
- ),
-};
-
-/**
- * Modals can also have a more emphasized backdrop overlay
- */
-export const HighEmphasis: StoryObj = {
- args: {
- overlayEmphasis: 'high',
- },
- render: Default.render,
-};
-
-/**
- * Modals can contain long, scrollable text. This is not recommended, however.
- */
-export const WithLongTextScrollable: StoryObj = {
- args: {
- isScrollable: true,
- },
- render: (args) => (
-
-
- Modal Title
- Modal Sub-title
-
-
- Title text
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc aliquam
- diam quis dolor maximus, non tincidunt lacus facilisis. Praesent ac
- vestibulum diam. Sed ac orci fringilla, ullamcorper quam nec,
- elementum turpis. Orci varius natoque penatibus et magnis dis
- parturient montes, nascetur ridiculus mus. Curabitur et vulputate leo.
- Phasellus convallis ante at augue iaculis, quis consectetur dolor
- placerat. Nulla ornare malesuada elit eu faucibus. Mauris ultricies
- tincidunt eleifend. Aliquam erat volutpat. Morbi nec ipsum sed sem
- facilisis tristique nec et ligula. Vivamus id feugiat sapien.
-
- Title text
-
- Nam ac tincidunt arcu. Nam non metus sem. Morbi eleifend metus vel
- venenatis accumsan. Vestibulum pharetra, ante quis sollicitudin
- aliquam, orci ex pretium ipsum, congue rhoncus dui orci sed velit.
- Cras ac leo vel massa rutrum auctor eget sed orci. Aenean id nisi
- consectetur, dapibus tellus ut, finibus metus. Integer tristique est
- vitae lectus suscipit, ut vulputate est fringilla. Donec pharetra
- facilisis erat at venenatis. Etiam faucibus dignissim leo eget congue.
- Sed vehicula imperdiet neque id gravida. Proin volutpat tortor quis
- quam molestie, faucibus condimentum ante sagittis. Suspendisse sit
- amet luctus tellus. Suspendisse a sapien hendrerit eros dictum
- faucibus. Vivamus pretium vel sem faucibus tristique. Integer iaculis
- pellentesque nunc ac pellentesque.
-
- Title text
-
- Integer mollis, urna eget sollicitudin laoreet, nunc elit facilisis
- urna, ut finibus est mi eu quam. Nam ac venenatis massa. Vestibulum
- suscipit ac ligula venenatis scelerisque. Fusce rutrum nulla lectus,
- sed dignissim ipsum faucibus sodales. Suspendisse id aliquet quam.
- Maecenas facilisis mauris dolor, id accumsan ex vehicula in. In nisl
- ligula, fringilla in enim nec, lobortis sollicitudin purus. Aliquam
- vehicula euismod enim quis finibus. Fusce ornare tortor malesuada,
- consequat magna quis, porta dolor. Donec quis nisl ac sem dictum
- semper quis vel turpis. Proin sed leo in ante rhoncus pellentesque a
- eu urna. Phasellus consequat lectus et hendrerit luctus. Lorem ipsum
- dolor sit amet, consectetur adipiscing elit.
-
- Title text
-
- Suspendisse vitae eros elit. Maecenas id urna tempus, tempus turpis
- id, blandit turpis. Fusce augue quam, pellentesque et suscipit
- consectetur, pharetra in mi. Suspendisse non ultricies purus. Integer
- dignissim condimentum sem ac porta. Vivamus viverra congue massa,
- vitae fermentum est scelerisque et. Duis a urna vitae odio semper
- dictum a sed nisl. In fringilla hendrerit massa, at luctus arcu
- tincidunt nec. Donec gravida, mauris sit amet porta lobortis, justo
- sem vehicula ipsum, at pretium sem leo sed libero. Morbi tristique
- rhoncus suscipit. Nullam at malesuada sapien. Nam in egestas tellus.
- Nulla quis metus dui. Suspendisse sit amet nisi at lectus ultricies
- egestas.
-
- Title text
-
- Integer pulvinar felis sit amet dignissim fermentum. Nulla sodales
- enim mi, varius feugiat sapien congue eget. Morbi vitae ipsum non
- ligula eleifend molestie. Aenean bibendum tortor sapien, quis volutpat
- ante ultricies id. Morbi varius dolor ac ante posuere, sit amet
- tincidunt lectus pulvinar. Proin id efficitur neque. Nullam vel
- feugiat dui. Curabitur imperdiet lacinia eros, ac iaculis odio.
- Praesent quis pretium sapien, quis posuere lectus. Proin eleifend
- purus nec massa aliquam commodo. Quisque auctor suscipit ex sed
- tristique. Sed eget ultrices est. Suspendisse nunc justo, dapibus at
- eros ac, rutrum vestibulum est.
-
-
-
-
- {}} rank="primary">
- Primary Action
-
- {}} rank="secondary">
- Secondary
-
-
-
-
- ),
-};
-
-/**
- * The default modal experience's Content area (the modal itself). The following stories show how the modal
- * will render in various contexts and with various props set.
- *
- * When viewing code, This will use `` directly, to demonstrate composition. When building `Modal`s, use
- * `Modal.Title` instead.
- *
- * **NOTE**: The order of the buttons puts the primary to the far bottom and right of the modal, per
- * macOS conventions and style guide.
- */
-export const ContentDefault: Story = {
- render: (args) => (
-
- ),
-};
-
-/**
- * Buttons can have a vertical alignment in the footer. For this, use `ButtonGroup` with the `buttonLayout` = `vertical`.
- * Make sure to match the Button width style by using `isFullWidth`.
- */
-export const LayoutVerticalWithTertiary: Story = {
- ...ContentDefault,
- args: {
- ...ContentDefault.args,
- size: 'lg',
- children: (
- <>
-
- Modal Title
- Modal Sub-title
-
-
-
-
- );
-};
-
-/**
- * `import {Modal} from "@chanzuckerberg/eds";`
- *
- * EDS Wrapper for the HeadlessUI Dialog component
- * @see https://headlessui.dev/react/dialog
- *
- * NOTE: You must have at least one focusable element in the modal contents, for keyboard
- * accessibility reasons. (The close button counts as a focusable element.) Use `initialFocus`
- * to choose a different element.
- *
- */
-export const Modal = (props: ModalProps) => {
- const {
- 'aria-label': ariaLabel,
- initialFocus,
- modalContainerClassName,
- onClose,
- open,
- overlayEmphasis = 'low',
- ...rest
- } = props;
- const { children } = rest;
-
- if (process.env.NODE_ENV !== 'production') {
- const hasModalTitle = childrenHaveModalTitle(children);
- if (!hasModalTitle && !ariaLabel) {
- throw new Error(
- "You must use the Modal.Title helper component or pass in an aria-label when using the Modal. The Modal uses the Modal.Title to describe the modal to screen readers using aria-labelledby. If you're not using the Modal.Title component, you can pass in an aria-label instead.",
- );
- }
- }
-
- const componentClassName = clsx(styles['modal'], modalContainerClassName);
-
- return (
-
-
-
- );
-};
-
-/**
- * Component defines the body of the modal.
- */
-const ModalBody = ({
- children,
- className,
- isFocusable,
- ...other
-}: ModalBodyProps) => (
-
- );
-};
-
-/**
- * Component defines the Title section of the modal.
- */
-const ModalTitle = ({
- children,
- className,
- preset = 'headline-md',
- ...other
-}: ModalTitleProps) => (
-
-
- {children}
-
-
-);
-
-const ModalSubTitle = ({
- children,
- className,
- preset = 'headline-sm',
- ...other
-}: ModalSubTitleProps) => {
- const componentClassName = clsx(styles['modal-sub-title'], className);
- return (
-
- {children}
-
- );
-};
-
-/**
- * Variations of the subcomponent to pass props from parent Modal component.
- * Same prop passed directly to subcomponent has priority over prop passed from Modal component.
- */
-const VariantModalHeader = (props: ModalHeaderProps) => {
- return ;
-};
-
-const FocusableModalBody = (props: ModalBodyProps) => {
- const { isScrollable } = React.useContext(ModalContext);
- return ;
-};
-
-const StickyModalFooter = (props: ModalFooterProps) => {
- const { isScrollable } = React.useContext(ModalContext);
- return ;
-};
-
-Modal.displayName = 'Modal';
-VariantModalHeader.displayName = 'Modal.Header';
-ModalTitle.displayName = 'Modal.Title';
-ModalSubTitle.displayName = 'Modal.SubTitle';
-FocusableModalBody.displayName = 'Modal.Body';
-StickyModalFooter.displayName = 'Modal.Footer';
-
-Modal.Header = VariantModalHeader;
-Modal.Title = ModalTitle;
-Modal.SubTitle = ModalSubTitle;
-Modal.Body = FocusableModalBody;
-Modal.Footer = StickyModalFooter;
diff --git a/src/components/Modal/Modal.module.css b/src/components/Modal/Modal.module.css
index 04c37ef0e..247adefb8 100755
--- a/src/components/Modal/Modal.module.css
+++ b/src/components/Modal/Modal.module.css
@@ -3,6 +3,9 @@
/*------------------------------------*\
# MODAL
\*------------------------------------*/
+/**
+ * TODO: Icon inherits color from the surrounding text, but should use the matching -icon- tokens from below
+ */
/**
* The modal wrapper and overlay which takes up the entire screen.
@@ -20,8 +23,13 @@
* The inverted background of the modal to provide contrast with the actual modal.
*/
.modal__overlay {
- background-color: var(--eds-theme-color-body-background-inverted);
- opacity: 0.5;
+ &.modal__overlay--emphasis-high {
+ background-color: rgb(from var(--eds-theme-color-background-utility-overlay-high-emphasis) r g b / 0.8);
+ }
+
+ &.modal__overlay--emphasis-low {
+ background-color: rgb(from var(--eds-theme-color-background-utility-overlay-low-emphasis) r g b / 0.5);
+ }
}
/**
@@ -32,9 +40,10 @@
align-items: center;
justify-content: center;
padding: calc(var(--eds-size-2) / 16 * 1rem);
+
/**
* Ensures modal is above other components. This is not a design token for now since we need to align on
- * z-indeces across the app and in the platform. https://app.shortcut.com/czi-edu/story/203871.
+ * z-indeces across the system
*/
z-index: 1050;
}
@@ -48,21 +57,26 @@
transition: none;
}
}
+
.modal__transition--enterFrom {
opacity: 0;
}
+
.modal__transition--enterTo {
opacity: 1;
}
+
.modal__transition--leave {
transition: opacity calc(var(--eds-anim-fade-long) * 1s) var(--eds-anim-ease);
@media (prefers-reduced-motion) {
transition: none;
}
}
+
.modal__transition--leaveFrom {
opacity: 1;
}
+
.modal__transition--leaveTo {
opacity: 0;
}
@@ -72,51 +86,79 @@
*/
.modal__content {
position: relative;
- height: 43.125rem;
- max-height: 90vh;
overflow: hidden;
- border-radius: calc(var(--eds-border-radius-lg) * 1px);
- background-color: var(--eds-theme-color-background-neutral-default);
+
/**
* This transparent border is for Window High Contrast Mode, which removes all
* background colors but makes borders 100% opacity black. Without this, the
* modal would have no clear boundary.
*/
- border: var(--eds-theme-form-border-width) transparent
- var(--eds-theme-color-background-neutral-default);
- /**
- * We use flexbox to handle height and width automatically.
- * The body can scroll when content goes beyond the component,
- * while the footer remains static (if scrollable).
- */
+ border: var(--eds-theme-form-border-width) transparent var(--eds-theme-color-background-utility-container);
+
display: flex;
flex-direction: column;
-
- width: 22.5rem;
- --modal-horizontal-padding: calc(var(--eds-size-4) / 16 * 1rem);
+
+ background-color: var(--eds-theme-color-background-utility-container);
}
/**
- * The medium modal size used for the md modal.
- * Also used for the lg modal size for when the screen size is smaller than 75rem.
+ * The large modal size used for the lg/default modal.
*/
-.modal__content--md {
+.modal__content--lg {
+ @media all and (min-width: $eds-bp-xs) {
+ width: 100%;
+ height: 100%;
+ }
+
+ @media all and (min-width: $eds-bp-sm) {
+ max-height: 40rem;
+ max-width: 50rem;
+ }
+
@media all and (min-width: $eds-bp-md) {
- width: 42rem;
- --modal-horizontal-padding: calc(var(--eds-size-6) / 16 * 1rem);
+ max-height: 40rem;
+ max-width: 50rem;
+ }
+
+ @media all and (min-width: $eds-bp-lg) {
+ max-height: 40rem;
+ max-width: 60rem;
+ }
+
+ @media all and (min-width: $eds-bp-xl) {
+ max-height: 40rem;
+ max-width: 60rem;
+ --modal-horizontal-padding: calc(var(--eds-size-8) / 16 * 1rem);
}
}
/**
- * The large modal size used for the lg/default modal.
+ * The small modal size used for the modal.
*/
-.modal__content--lg {
+ .modal__content--sm {
+ @media all and (min-width: $eds-bp-xs) {
+ max-height: 30rem;
+ max-width: 35rem;
+ }
+
+ @media all and (min-width: $eds-bp-sm) {
+ max-height: 30rem;
+ max-width: 25rem;
+ }
+
+ @media all and (min-width: $eds-bp-md) {
+ max-height: 30rem;
+ max-width: 25rem;
+ }
+
@media all and (min-width: $eds-bp-xl) {
- width: 64rem;
+ max-height: 30rem;
+ max-width: 35rem;
--modal-horizontal-padding: calc(var(--eds-size-8) / 16 * 1rem);
}
}
+
/**
* Allows scrolling of the modal content except for sticky footer.
* This functionality is our intended scroll behavior but consuming teams can
@@ -130,37 +172,9 @@
* The modal close button.
*/
.modal__close-button {
- border: 0;
- background-color: transparent;
-
position: absolute;
top: 0;
right: 0;
-
- /* Minimum button target size is 44px, but 3rem (48px) is next closest ramp size. */
- width: calc(var(--eds-size-6) / 16 * 1rem);
- height: calc(var(--eds-size-6) / 16 * 1rem);
-
- cursor: pointer;
-
- z-index: 1;
-}
-
-/**
- * The modal close icon that resides in the close button.
- */
-.modal__close-icon {
- position: absolute;
- top: calc(var(--eds-size-1) / 16 * 1rem);
- right: calc(var(--eds-size-1) / 16 * 1rem);
- color: var(--eds-theme-color-icon-neutral-subtle);
-}
-
-/**
- * White variant of the close icon in a brand variant of the modal.
- */
-.modal__close-icon--brand {
- color: var(--eds-theme-color-icon-neutral-default-inverse);
}
/*------------------------------------*\
@@ -171,18 +185,8 @@
* The body of the modal
*/
.modal-body {
- padding: calc(var(--eds-size-4) / 16 * 1rem) var(--modal-horizontal-padding);
flex: 1;
-}
-
-.modal-body:focus-visible {
- @mixin focusInside;
-}
-
-@supports not selector(:focus-visible) {
- .modal-body:focus {
- @mixin focusInside;
- }
+ padding: 0 calc(var(--eds-size-4) / 16 * 1rem);
}
/*------------------------------------*\
@@ -193,27 +197,18 @@
* Footer for the modal.
*/
.modal-footer {
- padding-top: calc(var(--eds-size-2-and-half) / 16 * 1rem);
- padding-bottom: calc(var(--eds-size-2-and-half) / 16 * 1rem);
- padding-left: var(--modal-horizontal-padding);
- padding-right: calc(var(--eds-size-3) / 16 * 1rem);
-
width: 100%;
- background-color: var(--eds-theme-color-background-neutral-default);
+ z-index: 1000;
- border-bottom-left-radius: calc(var(--eds-border-radius-lg) * 1px);
- border-bottom-right-radius: calc(var(--eds-border-radius-lg) * 1px);
+ padding: calc(var(--eds-size-3) / 16 * 1rem) calc(var(--eds-size-4) / 16 * 1rem);
- z-index: 100;
+ background-color: var(--eds-theme-color-background-utility-container);
}
-/**
- * Sticky variant of the footer.
- * Used when the modal variant is scrollable.
- */
.modal-footer--sticky {
position: sticky;
bottom: 0;
+
box-shadow: var(--eds-box-shadow-xl);
}
@@ -226,89 +221,10 @@
*/
.modal-header {
width: 100%;
- background-color: var(--eds-theme-color-background-neutral-default);
- padding-top: calc(var(--eds-size-7) / 16 * 1rem);
- padding-left: var(--modal-horizontal-padding);
- padding-right: var(--modal-horizontal-padding);
-}
-
-/**
- * Brand variant for the header.
- */
-.modal-header--brand {
- display: flex;
- flex-direction: column;
-
- min-height: 10.75rem;
-
- @media all and (min-height: $eds-bp-sm) {
- min-height: 19.75rem;
- }
- @media all and (min-width: $eds-bp-md) and (min-height: $eds-bp-sm) {
- flex-direction: row;
- }
- flex-shrink: 0;
-
- color: var(--eds-theme-color-text-neutral-default-inverse);
- background-color: var(--eds-theme-color-modal-brand-header-background);
- h2 {
- /**
- * Brand specific font for the title.
- */
- flex: 1;
- font: var(--eds-theme-typography-headline-secondary-lg);
-
- @media all and (min-width: $eds-bp-sm) {
- margin-bottom: calc(var(--eds-size-3) / 16 * 1rem);
- }
- @media all and (min-width: $eds-bp-xl) {
- font: var(--eds-theme-typography-headline-secondary-md);
-
- }
- }
-}
-
-/**
- * Specific asset placement for brand.
- */
-.modal-header__brand-asset {
- align-self: flex-end;
- position: relative;
- top: calc(var(--eds-size-2) / 16 * 1rem);
- left: calc(var(--eds-size-2) / 16 * 1rem);
-
- width: 8.5rem;
- height: 8.5rem;
-
- /* For mobile landscape orientation. */
- @media all and (min-width: $eds-bp-sm) {
- display: none;
- }
-
- @media all and (min-width: $eds-bp-md) and (min-height: $eds-bp-sm) {
- display: block;
- width: 14rem;
- height: 14rem;
- left: calc(var(--eds-size-3) / 16 * 1rem);
- }
-
- @media all and (min-width: $eds-bp-xl) {
- width: 16.5rem;
- height: 16.5rem;
- left: 0;
- }
+ padding: calc(var(--eds-size-3) / 16 * 1rem) calc(var(--eds-size-4) / 16 * 1rem);
}
-/*------------------------------------*\
- # MODAL STEPPER
-\*------------------------------------*/
-
-/**
- * Stepper that resides in the modal footer.
- */
- .modal-stepper {
- color: var(--eds-theme-color-icon-neutral-default);
- display: flex;
- gap: calc(var(--eds-size-1-and-half) / 16 * 1rem);
-}
+.modal-sub-title {
+ color: var(--eds-theme-color-text-utility-default-secondary);
+}
\ No newline at end of file
diff --git a/src/components/Modal/Modal.stories.tsx b/src/components/Modal/Modal.stories.tsx
index a4c03963f..71930660b 100644
--- a/src/components/Modal/Modal.stories.tsx
+++ b/src/components/Modal/Modal.stories.tsx
@@ -1,11 +1,12 @@
import type { StoryObj, Meta } from '@storybook/react';
-import clsx from 'clsx';
-import type { ReactNode } from 'react';
import React from 'react';
import { useState } from 'react';
+
import { Modal, ModalContent } from './Modal';
-import { Button, ButtonGroup, Heading, Tooltip } from '../../';
+import { Heading, Text } from '../../';
import { chromaticViewports, storybookViewports } from '../../util/viewports';
+import Button from '../Button';
+import ButtonGroup from '../ButtonGroup';
export default {
title: 'Components/Modal',
@@ -14,8 +15,7 @@ export default {
// The modal is initially closed for most of these stories,
// which renders testing it for visual regressions unhelpful.
chromatic: { disableSnapshot: true },
- layout: 'centered',
- badges: ['intro-1.0'],
+ badges: ['intro-1.0', 'current-2.0'],
},
tags: ['autodocs'],
argTypes: {
@@ -41,55 +41,33 @@ export default {
'Toggles scrollable variant of the modal. If modal is scrollable, footer is not, and vice versa.',
type: 'boolean',
},
+ size: {
+ description: 'Max size of the modal, which responds to the viewport',
+ control: {
+ type: 'select',
+ },
+ options: ['sm', 'lg'],
+ defaultValue: 'lg',
+ },
},
+ decorators: [(Story) =>
{Story()}
],
} as Meta;
type Args = React.ComponentProps;
type Story = StoryObj;
type InteractiveArgs = Omit;
-const getChildren = (
- inDialogComponent = true,
- bodyContent: ReactNode = 'Modal content.',
- showStepper = false,
-) => (
- <>
- Brand Asset
}
- >
- {inDialogComponent ? (
- Modal Title
- ) : (
- /* Not using Modal.Title here because it can't exist outside of the Dialog component. */
- Modal Title
- )}
-
- {bodyContent}
-
- {showStepper && }
-
- {/* This has to be manually tested since Tooltip tests are flaky in Chromatic */}
- {}} status="neutral">
- Button 2
-
-
- {}} variant="primary">
- Button 1
-
-
-
-
- >
-);
-
+/**
+ * Wrapper for triggering `Modal` with a button
+ * @param args
+ * @returns
+ */
function InteractiveExample(args: InteractiveArgs) {
const [open, setOpen] = useState(false);
return (
<>
- setOpen(true)} variant="primary">
+ setOpen(true)} rank="primary">
Open the modal
@@ -106,119 +84,38 @@ function InteractiveExample(args: InteractiveArgs) {
*/
export const Default: StoryObj = {
render: (args) => (
- {getChildren()}
- ),
-};
-
-type HeadingArgs = React.ComponentProps;
-/**
- * Clicking on a trigger item (in this case `Button`) will cause a modal to open.
- *
- * **Note**: this only works from certain screens in Storybook. If it doesn't work as expected, view from the
- * "docs" sub-page.
- */
-export const ControlHeadingInteractive: StoryObj = {
- argTypes: {
- as: {
- control: 'select',
- name: 'title "as" prop',
- options: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
- },
- },
- render: ({ as, ...args }) => (
- Modal Title
+ Modal Title
+ Modal Sub-title
- Modal Content
+
+
Modal Content
+
+
+
+ {}} rank="primary">
+ Primary Action
+
+ {}} rank="secondary">
+ Secondary
+
+
+
),
- parameters: {
- /**
- * Purpose of this story is to have different controls for the Modal.Title but defaults to what all the other stories are.
- * Hence will snap similarly to the other stories has no value in snapping both unit and Chromatic.
- */
- snapshot: { skip: true },
- },
};
/**
- * You can disable the close button on the modal. This will require users to either click out of the modal, or hit escape to close.
- *
- * **NOTE**: this is less discoverable and should be avoided when possible.
+ * Modals can also have a more emphasized backdrop overlay
*/
-export const WithoutCloseButton: StoryObj = {
- render: (args) => (
-
- {getChildren()}
-
- ),
+export const HighEmphasis: StoryObj = {
+ args: {
+ overlayEmphasis: 'high',
+ },
+ render: Default.render,
};
-const reallyLongText = (
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc aliquam diam
- quis dolor maximus, non tincidunt lacus facilisis. Praesent ac vestibulum
- diam. Sed ac orci fringilla, ullamcorper quam nec, elementum turpis. Orci
- varius natoque penatibus et magnis dis parturient montes, nascetur
- ridiculus mus. Curabitur et vulputate leo. Phasellus convallis ante at
- augue iaculis, quis consectetur dolor placerat. Nulla ornare malesuada
- elit eu faucibus. Mauris ultricies tincidunt eleifend. Aliquam erat
- volutpat. Morbi nec ipsum sed sem facilisis tristique nec et ligula.
- Vivamus id feugiat sapien.
-
-
- Nam ac tincidunt arcu. Nam non metus sem. Morbi eleifend metus vel
- venenatis accumsan. Vestibulum pharetra, ante quis sollicitudin aliquam,
- orci ex pretium ipsum, congue rhoncus dui orci sed velit. Cras ac leo vel
- massa rutrum auctor eget sed orci. Aenean id nisi consectetur, dapibus
- tellus ut, finibus metus. Integer tristique est vitae lectus suscipit, ut
- vulputate est fringilla. Donec pharetra facilisis erat at venenatis. Etiam
- faucibus dignissim leo eget congue. Sed vehicula imperdiet neque id
- gravida. Proin volutpat tortor quis quam molestie, faucibus condimentum
- ante sagittis. Suspendisse sit amet luctus tellus. Suspendisse a sapien
- hendrerit eros dictum faucibus. Vivamus pretium vel sem faucibus
- tristique. Integer iaculis pellentesque nunc ac pellentesque.
-
-
- Integer mollis, urna eget sollicitudin laoreet, nunc elit facilisis urna,
- ut finibus est mi eu quam. Nam ac venenatis massa. Vestibulum suscipit ac
- ligula venenatis scelerisque. Fusce rutrum nulla lectus, sed dignissim
- ipsum faucibus sodales. Suspendisse id aliquet quam. Maecenas facilisis
- mauris dolor, id accumsan ex vehicula in. In nisl ligula, fringilla in
- enim nec, lobortis sollicitudin purus. Aliquam vehicula euismod enim quis
- finibus. Fusce ornare tortor malesuada, consequat magna quis, porta dolor.
- Donec quis nisl ac sem dictum semper quis vel turpis. Proin sed leo in
- ante rhoncus pellentesque a eu urna. Phasellus consequat lectus et
- hendrerit luctus. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
-
-
- Suspendisse vitae eros elit. Maecenas id urna tempus, tempus turpis id,
- blandit turpis. Fusce augue quam, pellentesque et suscipit consectetur,
- pharetra in mi. Suspendisse non ultricies purus. Integer dignissim
- condimentum sem ac porta. Vivamus viverra congue massa, vitae fermentum
- est scelerisque et. Duis a urna vitae odio semper dictum a sed nisl. In
- fringilla hendrerit massa, at luctus arcu tincidunt nec. Donec gravida,
- mauris sit amet porta lobortis, justo sem vehicula ipsum, at pretium sem
- leo sed libero. Morbi tristique rhoncus suscipit. Nullam at malesuada
- sapien. Nam in egestas tellus. Nulla quis metus dui. Suspendisse sit amet
- nisi at lectus ultricies egestas.
-
-
- Integer pulvinar felis sit amet dignissim fermentum. Nulla sodales enim
- mi, varius feugiat sapien congue eget. Morbi vitae ipsum non ligula
- eleifend molestie. Aenean bibendum tortor sapien, quis volutpat ante
- ultricies id. Morbi varius dolor ac ante posuere, sit amet tincidunt
- lectus pulvinar. Proin id efficitur neque. Nullam vel feugiat dui.
- Curabitur imperdiet lacinia eros, ac iaculis odio. Praesent quis pretium
- sapien, quis posuere lectus. Proin eleifend purus nec massa aliquam
- commodo. Quisque auctor suscipit ex sed tristique. Sed eget ultrices est.
- Suspendisse nunc justo, dapibus at eros ac, rutrum vestibulum est.
-
-
-);
-
/**
* Modals can contain long, scrollable text. This is not recommended, however.
*/
@@ -228,52 +125,108 @@ export const WithLongTextScrollable: StoryObj = {
},
render: (args) => (
- {getChildren(true, reallyLongText)}
-
- ),
-};
-
-/**
- * You can avoid rendering the header and footer in a modal
- */
-export const WithoutHeaderAndFooter: StoryObj = {
- args: {
- isScrollable: true,
- },
- render: (args) => (
-
- {reallyLongText}
+
+ Modal Title
+ Modal Sub-title
+
+
+ Title text
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc aliquam
+ diam quis dolor maximus, non tincidunt lacus facilisis. Praesent ac
+ vestibulum diam. Sed ac orci fringilla, ullamcorper quam nec,
+ elementum turpis. Orci varius natoque penatibus et magnis dis
+ parturient montes, nascetur ridiculus mus. Curabitur et vulputate leo.
+ Phasellus convallis ante at augue iaculis, quis consectetur dolor
+ placerat. Nulla ornare malesuada elit eu faucibus. Mauris ultricies
+ tincidunt eleifend. Aliquam erat volutpat. Morbi nec ipsum sed sem
+ facilisis tristique nec et ligula. Vivamus id feugiat sapien.
+
+ Title text
+
+ Nam ac tincidunt arcu. Nam non metus sem. Morbi eleifend metus vel
+ venenatis accumsan. Vestibulum pharetra, ante quis sollicitudin
+ aliquam, orci ex pretium ipsum, congue rhoncus dui orci sed velit.
+ Cras ac leo vel massa rutrum auctor eget sed orci. Aenean id nisi
+ consectetur, dapibus tellus ut, finibus metus. Integer tristique est
+ vitae lectus suscipit, ut vulputate est fringilla. Donec pharetra
+ facilisis erat at venenatis. Etiam faucibus dignissim leo eget congue.
+ Sed vehicula imperdiet neque id gravida. Proin volutpat tortor quis
+ quam molestie, faucibus condimentum ante sagittis. Suspendisse sit
+ amet luctus tellus. Suspendisse a sapien hendrerit eros dictum
+ faucibus. Vivamus pretium vel sem faucibus tristique. Integer iaculis
+ pellentesque nunc ac pellentesque.
+
+ Title text
+
+ Integer mollis, urna eget sollicitudin laoreet, nunc elit facilisis
+ urna, ut finibus est mi eu quam. Nam ac venenatis massa. Vestibulum
+ suscipit ac ligula venenatis scelerisque. Fusce rutrum nulla lectus,
+ sed dignissim ipsum faucibus sodales. Suspendisse id aliquet quam.
+ Maecenas facilisis mauris dolor, id accumsan ex vehicula in. In nisl
+ ligula, fringilla in enim nec, lobortis sollicitudin purus. Aliquam
+ vehicula euismod enim quis finibus. Fusce ornare tortor malesuada,
+ consequat magna quis, porta dolor. Donec quis nisl ac sem dictum
+ semper quis vel turpis. Proin sed leo in ante rhoncus pellentesque a
+ eu urna. Phasellus consequat lectus et hendrerit luctus. Lorem ipsum
+ dolor sit amet, consectetur adipiscing elit.
+
+ Title text
+
+ Suspendisse vitae eros elit. Maecenas id urna tempus, tempus turpis
+ id, blandit turpis. Fusce augue quam, pellentesque et suscipit
+ consectetur, pharetra in mi. Suspendisse non ultricies purus. Integer
+ dignissim condimentum sem ac porta. Vivamus viverra congue massa,
+ vitae fermentum est scelerisque et. Duis a urna vitae odio semper
+ dictum a sed nisl. In fringilla hendrerit massa, at luctus arcu
+ tincidunt nec. Donec gravida, mauris sit amet porta lobortis, justo
+ sem vehicula ipsum, at pretium sem leo sed libero. Morbi tristique
+ rhoncus suscipit. Nullam at malesuada sapien. Nam in egestas tellus.
+ Nulla quis metus dui. Suspendisse sit amet nisi at lectus ultricies
+ egestas.
+
+ Title text
+
+ Integer pulvinar felis sit amet dignissim fermentum. Nulla sodales
+ enim mi, varius feugiat sapien congue eget. Morbi vitae ipsum non
+ ligula eleifend molestie. Aenean bibendum tortor sapien, quis volutpat
+ ante ultricies id. Morbi varius dolor ac ante posuere, sit amet
+ tincidunt lectus pulvinar. Proin id efficitur neque. Nullam vel
+ feugiat dui. Curabitur imperdiet lacinia eros, ac iaculis odio.
+ Praesent quis pretium sapien, quis posuere lectus. Proin eleifend
+ purus nec massa aliquam commodo. Quisque auctor suscipit ex sed
+ tristique. Sed eget ultrices est. Suspendisse nunc justo, dapibus at
+ eros ac, rutrum vestibulum est.
+
+
+
+
+ {}} rank="primary">
+ Primary Action
+
+ {}} rank="secondary">
+ Secondary
+
+
+
),
};
-export const ModalStepper: StoryObj<
- React.ComponentProps
-> = {
- args: {
- activeStep: 1,
- totalSteps: 3,
- },
- render: (args) => ,
- decorators: [(Story) =>
{Story()}
],
- parameters: {
- chromatic: { disableSnapshot: false },
- },
-};
/**
* The default modal experience's Content area (the modal itself). The following stories show how the modal
* will render in various contexts and with various props set.
*
+ * When viewing code, This will use `` directly, to demonstrate composition. When building `Modal`s, use
+ * `Modal.Title` instead.
+ *
* **NOTE**: The order of the buttons puts the primary to the far bottom and right of the modal, per
* macOS conventions and style guide.
*/
export const ContentDefault: Story = {
render: (args) => (
+
+
+
+ {}} rank="primary">
+ Primary Action
+
+ {}} rank="secondary">
+ Secondary
+
+
+
+ >
+ ),
hideCloseButton: false,
open: true,
},
@@ -294,13 +267,13 @@ export const ContentDefault: Story = {
/**
* `Modal` provides `size`, which allows control over the natural width of the modal. This does not affect the contents
- * of the modal.
+ * of the modal except to wrap text.
*/
-export const Medium: Story = {
+export const Large: Story = {
...ContentDefault,
args: {
...ContentDefault.args,
- size: 'md',
+ size: 'lg',
},
};
@@ -316,16 +289,133 @@ export const Small: Story = {
};
/**
- * The brand variant offers a catered experience with a slot for a hero image to be attached to the modal.
+ * Buttons can have a vertical alignment in the footer. For this, use `ButtonGroup` with the `buttonLayout` = `vertical`.
+ * Make sure to match the Button width style by using `isFullWidth`.
*/
-export const Brand: Story = {
+export const LayoutVertical: Story = {
...ContentDefault,
args: {
...ContentDefault.args,
- variant: 'brand',
+ size: 'lg',
+ children: (
+ <>
+
+ Modal Title
+ Modal Sub-title
+
+
+
+ ),
+};
+
+/**
+ * Buttons can have a vertical alignment in the footer. For this, use `ButtonGroup` with the `buttonLayout` = `vertical`.
+ * Make sure to match the Button width style by using `isFullWidth`.
+ */
+export const LayoutVerticalWithTertiary: Story = {
+ ...ContentDefault,
+ args: {
+ ...ContentDefault.args,
+ size: 'lg',
+ children: (
+ <>
+
+ Modal Title
+ Modal Sub-title
+
+
+
+ ),
};
+/**
+ * This shows the responsive layout in a mobile viewport
+ */
export const Mobile: Story = {
...ContentDefault,
parameters: {
@@ -340,6 +430,9 @@ export const Mobile: Story = {
},
};
+/**
+ * This shows the responsive layout in a mobile (landscape) viewport
+ */
export const MobileLandscape: Story = {
...ContentDefault,
parameters: {
@@ -363,22 +456,9 @@ export const MobileLandscape: Story = {
},
};
-export const MobileBrand: Story = {
- ...Mobile,
- args: {
- ...Mobile.args,
- variant: 'brand',
- },
-};
-
-export const MobileLandscapeBrand: Story = {
- ...MobileLandscape,
- args: {
- ...MobileLandscape.args,
- variant: 'brand',
- },
-};
-
+/**
+ * This shows the responsive layout in a tablet viewport
+ */
export const Tablet: Story = {
...ContentDefault,
parameters: {
@@ -395,26 +475,3 @@ export const Tablet: Story = {
},
},
};
-
-export const TabletBrand: Story = {
- ...Tablet,
- args: {
- ...Tablet.args,
- variant: 'brand',
- },
-};
-
-/**
- * `Modal` can make use of the embedded `.Stepper` sub-component, to implement wizard behavior.
- */
-export const WithStepper: Story = {
- ...ContentDefault,
- args: {
- ...ContentDefault.args,
- children: getChildren(
- false,
- 'Modal body content. This is an example use case with the stepper in the footer.',
- true,
- ),
- },
-};
diff --git a/src/components/Modal/Modal.test.tsx b/src/components/Modal/Modal.test.tsx
index c4e762b54..fac6bff53 100644
--- a/src/components/Modal/Modal.test.tsx
+++ b/src/components/Modal/Modal.test.tsx
@@ -60,7 +60,7 @@ describe('Modal', () => {
});
await user.click(openModalButton);
const closeButton = await screen.findByRole('button', {
- name: 'close modal',
+ name: 'close',
});
await user.click(closeButton);
await waitFor(() => {
diff --git a/src/components/Modal/Modal.tsx b/src/components/Modal/Modal.tsx
index d660b801b..c7a4ba4ff 100644
--- a/src/components/Modal/Modal.tsx
+++ b/src/components/Modal/Modal.tsx
@@ -2,15 +2,18 @@ import { Dialog, Transition } from '@headlessui/react';
import clsx from 'clsx';
import type { MutableRefObject, ReactNode } from 'react';
import React from 'react';
+
import type { ExtractProps } from '../../util/utility-types';
import type { Size } from '../../util/variant-types';
+
+import Button from '../Button';
import Heading from '../Heading';
-import { Icon, type IconName } from '../Icon/Icon';
-import styles from './Modal.module.css';
+import Text from '../Text';
-type Variant = 'brand';
+import styles from './Modal.module.css';
type ModalContentProps = {
+ // Component API
/**
* Optional aria-label for the modal.
*
@@ -74,17 +77,19 @@ type ModalContentProps = {
* ```
*/
onClose: () => void;
+ // Design API
/**
- * Max size of the modal. Defaults to 'lg'.
- * Will still break responsively.
+ * Max size of the modal, which responds to the viewport
*
* **Default is `"lg"`**.
*/
- size?: Extract;
+ size?: Extract;
/**
- * Color variants of the modal.
+ * Emphasis used on the backgound overlay (behind the modal)
+ *
+ * **Default is `"low"`**.
*/
- variant?: Variant;
+ overlayEmphasis?: 'low' | 'high';
};
type ModalProps = ModalContentProps & {
@@ -110,6 +115,19 @@ type ModalProps = ModalContentProps & {
};
type ModalTitleProps = ExtractProps & {
+ // Component API
+ /**
+ * Contents for the modal title.
+ */
+ children: ReactNode;
+ /**
+ * CSS class names that can be appended to the component.
+ */
+ className?: string;
+};
+
+type ModalSubTitleProps = ExtractProps & {
+ // Component API
/**
* Contents for the modal title.
*/
@@ -121,6 +139,7 @@ type ModalTitleProps = ExtractProps & {
};
type ModalBodyProps = {
+ // Component API
/**
* Child node(s) that can be nested inside component. `Modal.Header`,
* `Modal.Body`, and `Model.Footer` are the only permissible children of the Modal.
@@ -130,6 +149,7 @@ type ModalBodyProps = {
* CSS class names that can be appended to the component.
*/
className?: string;
+ // Design API
/**
* Toggles focusable variant of the modal. Used to attach a tabIndex for keyboard scrolling
* and focus indicator outline.
@@ -140,6 +160,7 @@ type ModalBodyProps = {
};
type ModalHeaderProps = {
+ // Component API
/**
* Child node(s) to place inside the Modal header.
* Should include the
@@ -149,21 +170,11 @@ type ModalHeaderProps = {
* CSS class names that can be appended to the component.
*/
className?: string;
- /**
- * Adjusts height, color, and text of the header.
- */
- variant?: Variant;
- /**
- * Placeholder for brand asset.
- */
- brandAsset?: ReactNode;
- /**
- * CSS class names that can be appended to the brand asset.
- */
- assetClassName?: string;
+ // Design API
};
type ModalFooterProps = {
+ // Component API
/**
* Child node(s) to place inside the Modal footer.
*/
@@ -172,6 +183,7 @@ type ModalFooterProps = {
* CSS class names that can be appended to the component.
*/
className?: string;
+ // Design API
/**
* Toggles sticky variant of the footer. If modal is scrollable, footer is sticky.
* Also adds border and shadow to indicate sticky status.
@@ -180,25 +192,8 @@ type ModalFooterProps = {
isSticky?: boolean;
};
-type ModalStepperProps = {
- /**
- * Indicates which step is the active step. Must be one or more.
- */
- activeStep: number;
- /**
- * CSS class names that can be appended to the component.
- */
- className?: string;
- /**
- * Indicates how many steps to represent. Must be one or more and
- * greater than or equal to activeStep.
- */
- totalSteps: number;
-};
-
type Context = {
isScrollable?: boolean;
- variant?: Variant;
};
const ModalContext = React.createContext({});
@@ -225,7 +220,7 @@ function childrenHaveModalTitle(children?: ReactNode): boolean {
/**
* The actual modal, without the dark overlay behind it.
*
- * This is only for testing purposes; please do not import and use this directly.
+ * This is only exported for testing purposes; please do not import and use this directly.
*/
export const ModalContent = (props: ModalContentProps) => {
const {
@@ -235,38 +230,33 @@ export const ModalContent = (props: ModalContentProps) => {
isScrollable,
onClose,
size = 'lg',
- variant,
...other
} = props;
const componentClassName = clsx(
styles['modal__content'],
isScrollable && styles['modal__content--scrollable'],
- (size === 'md' || size === 'lg') && styles['modal__content--md'],
- size === 'lg' && styles['modal__content--lg'],
+ size && styles[`modal__content--${size}`],
className,
);
- const closeIconClassName = clsx(
- styles['modal__close-icon'],
- variant === 'brand' && styles['modal__close-icon--brand'],
- );
-
return (
-
+
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc aliquam diam quis dolor maximus, non tincidunt lacus facilisis. Praesent ac vestibulum diam. Sed ac orci fringilla, ullamcorper quam nec, elementum turpis. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Curabitur et vulputate leo. Phasellus convallis ante at augue iaculis, quis consectetur dolor placerat. Nulla ornare malesuada elit eu faucibus. Mauris ultricies tincidunt eleifend. Aliquam erat volutpat. Morbi nec ipsum sed sem facilisis tristique nec et ligula. Vivamus id feugiat sapien.
-
-
- Title text
-
-
- Nam ac tincidunt arcu. Nam non metus sem. Morbi eleifend metus vel venenatis accumsan. Vestibulum pharetra, ante quis sollicitudin aliquam, orci ex pretium ipsum, congue rhoncus dui orci sed velit. Cras ac leo vel massa rutrum auctor eget sed orci. Aenean id nisi consectetur, dapibus tellus ut, finibus metus. Integer tristique est vitae lectus suscipit, ut vulputate est fringilla. Donec pharetra facilisis erat at venenatis. Etiam faucibus dignissim leo eget congue. Sed vehicula imperdiet neque id gravida. Proin volutpat tortor quis quam molestie, faucibus condimentum ante sagittis. Suspendisse sit amet luctus tellus. Suspendisse a sapien hendrerit eros dictum faucibus. Vivamus pretium vel sem faucibus tristique. Integer iaculis pellentesque nunc ac pellentesque.
-
-
- Title text
-
-
- Integer mollis, urna eget sollicitudin laoreet, nunc elit facilisis urna, ut finibus est mi eu quam. Nam ac venenatis massa. Vestibulum suscipit ac ligula venenatis scelerisque. Fusce rutrum nulla lectus, sed dignissim ipsum faucibus sodales. Suspendisse id aliquet quam. Maecenas facilisis mauris dolor, id accumsan ex vehicula in. In nisl ligula, fringilla in enim nec, lobortis sollicitudin purus. Aliquam vehicula euismod enim quis finibus. Fusce ornare tortor malesuada, consequat magna quis, porta dolor. Donec quis nisl ac sem dictum semper quis vel turpis. Proin sed leo in ante rhoncus pellentesque a eu urna. Phasellus consequat lectus et hendrerit luctus. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
-
-
- Title text
-
-
- Suspendisse vitae eros elit. Maecenas id urna tempus, tempus turpis id, blandit turpis. Fusce augue quam, pellentesque et suscipit consectetur, pharetra in mi. Suspendisse non ultricies purus. Integer dignissim condimentum sem ac porta. Vivamus viverra congue massa, vitae fermentum est scelerisque et. Duis a urna vitae odio semper dictum a sed nisl. In fringilla hendrerit massa, at luctus arcu tincidunt nec. Donec gravida, mauris sit amet porta lobortis, justo sem vehicula ipsum, at pretium sem leo sed libero. Morbi tristique rhoncus suscipit. Nullam at malesuada sapien. Nam in egestas tellus. Nulla quis metus dui. Suspendisse sit amet nisi at lectus ultricies egestas.
-
-
- Title text
-
-
- Integer pulvinar felis sit amet dignissim fermentum. Nulla sodales enim mi, varius feugiat sapien congue eget. Morbi vitae ipsum non ligula eleifend molestie. Aenean bibendum tortor sapien, quis volutpat ante ultricies id. Morbi varius dolor ac ante posuere, sit amet tincidunt lectus pulvinar. Proin id efficitur neque. Nullam vel feugiat dui. Curabitur imperdiet lacinia eros, ac iaculis odio. Praesent quis pretium sapien, quis posuere lectus. Proin eleifend purus nec massa aliquam commodo. Quisque auctor suscipit ex sed tristique. Sed eget ultrices est. Suspendisse nunc justo, dapibus at eros ac, rutrum vestibulum est.
-
-
-
-
-
-`;
diff --git a/src/components/Modal/__snapshots__/Modal.test.tsx.snap b/src/components/Modal/__snapshots__/Modal.test.tsx.snap
index 7fc970554..1bf94df45 100644
--- a/src/components/Modal/__snapshots__/Modal.test.tsx.snap
+++ b/src/components/Modal/__snapshots__/Modal.test.tsx.snap
@@ -1,34 +1,37 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`Modal Brand story renders snapshot 1`] = `
+exports[`Modal ContentDefault story renders snapshot 1`] = `
-
-
- close modal
-
-
-
+
+
+
+
-
- Brand Asset
-
+ Modal Sub-title
- Modal content.
+
+ Modal Content
+
`;
-exports[`Modal ContentDefault story renders snapshot 1`] = `
+exports[`Modal Default story renders snapshot 1`] = `
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc aliquam diam quis dolor maximus, non tincidunt lacus facilisis. Praesent ac vestibulum diam. Sed ac orci fringilla, ullamcorper quam nec, elementum turpis. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Curabitur et vulputate leo. Phasellus convallis ante at augue iaculis, quis consectetur dolor placerat. Nulla ornare malesuada elit eu faucibus. Mauris ultricies tincidunt eleifend. Aliquam erat volutpat. Morbi nec ipsum sed sem facilisis tristique nec et ligula. Vivamus id feugiat sapien.
-
-
- Nam ac tincidunt arcu. Nam non metus sem. Morbi eleifend metus vel venenatis accumsan. Vestibulum pharetra, ante quis sollicitudin aliquam, orci ex pretium ipsum, congue rhoncus dui orci sed velit. Cras ac leo vel massa rutrum auctor eget sed orci. Aenean id nisi consectetur, dapibus tellus ut, finibus metus. Integer tristique est vitae lectus suscipit, ut vulputate est fringilla. Donec pharetra facilisis erat at venenatis. Etiam faucibus dignissim leo eget congue. Sed vehicula imperdiet neque id gravida. Proin volutpat tortor quis quam molestie, faucibus condimentum ante sagittis. Suspendisse sit amet luctus tellus. Suspendisse a sapien hendrerit eros dictum faucibus. Vivamus pretium vel sem faucibus tristique. Integer iaculis pellentesque nunc ac pellentesque.
-
-
- Integer mollis, urna eget sollicitudin laoreet, nunc elit facilisis urna, ut finibus est mi eu quam. Nam ac venenatis massa. Vestibulum suscipit ac ligula venenatis scelerisque. Fusce rutrum nulla lectus, sed dignissim ipsum faucibus sodales. Suspendisse id aliquet quam. Maecenas facilisis mauris dolor, id accumsan ex vehicula in. In nisl ligula, fringilla in enim nec, lobortis sollicitudin purus. Aliquam vehicula euismod enim quis finibus. Fusce ornare tortor malesuada, consequat magna quis, porta dolor. Donec quis nisl ac sem dictum semper quis vel turpis. Proin sed leo in ante rhoncus pellentesque a eu urna. Phasellus consequat lectus et hendrerit luctus. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
-
-
- Suspendisse vitae eros elit. Maecenas id urna tempus, tempus turpis id, blandit turpis. Fusce augue quam, pellentesque et suscipit consectetur, pharetra in mi. Suspendisse non ultricies purus. Integer dignissim condimentum sem ac porta. Vivamus viverra congue massa, vitae fermentum est scelerisque et. Duis a urna vitae odio semper dictum a sed nisl. In fringilla hendrerit massa, at luctus arcu tincidunt nec. Donec gravida, mauris sit amet porta lobortis, justo sem vehicula ipsum, at pretium sem leo sed libero. Morbi tristique rhoncus suscipit. Nullam at malesuada sapien. Nam in egestas tellus. Nulla quis metus dui. Suspendisse sit amet nisi at lectus ultricies egestas.
-
-
- Integer pulvinar felis sit amet dignissim fermentum. Nulla sodales enim mi, varius feugiat sapien congue eget. Morbi vitae ipsum non ligula eleifend molestie. Aenean bibendum tortor sapien, quis volutpat ante ultricies id. Morbi varius dolor ac ante posuere, sit amet tincidunt lectus pulvinar. Proin id efficitur neque. Nullam vel feugiat dui. Curabitur imperdiet lacinia eros, ac iaculis odio. Praesent quis pretium sapien, quis posuere lectus. Proin eleifend purus nec massa aliquam commodo. Quisque auctor suscipit ex sed tristique. Sed eget ultrices est. Suspendisse nunc justo, dapibus at eros ac, rutrum vestibulum est.
-
- Modal content.
+ Suspendisse vitae eros elit. Maecenas id urna tempus, tempus turpis id, blandit turpis. Fusce augue quam, pellentesque et suscipit consectetur, pharetra in mi. Suspendisse non ultricies purus. Integer dignissim condimentum sem ac porta. Vivamus viverra congue massa, vitae fermentum est scelerisque et. Duis a urna vitae odio semper dictum a sed nisl. In fringilla hendrerit massa, at luctus arcu tincidunt nec. Donec gravida, mauris sit amet porta lobortis, justo sem vehicula ipsum, at pretium sem leo sed libero. Morbi tristique rhoncus suscipit. Nullam at malesuada sapien. Nam in egestas tellus. Nulla quis metus dui. Suspendisse sit amet nisi at lectus ultricies egestas.
+
+
+ Title text
+
+
+ Integer pulvinar felis sit amet dignissim fermentum. Nulla sodales enim mi, varius feugiat sapien congue eget. Morbi vitae ipsum non ligula eleifend molestie. Aenean bibendum tortor sapien, quis volutpat ante ultricies id. Morbi varius dolor ac ante posuere, sit amet tincidunt lectus pulvinar. Proin id efficitur neque. Nullam vel feugiat dui. Curabitur imperdiet lacinia eros, ac iaculis odio. Praesent quis pretium sapien, quis posuere lectus. Proin eleifend purus nec massa aliquam commodo. Quisque auctor suscipit ex sed tristique. Sed eget ultrices est. Suspendisse nunc justo, dapibus at eros ac, rutrum vestibulum est.
+
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc aliquam diam quis dolor maximus, non tincidunt lacus facilisis. Praesent ac vestibulum diam. Sed ac orci fringilla, ullamcorper quam nec, elementum turpis. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Curabitur et vulputate leo. Phasellus convallis ante at augue iaculis, quis consectetur dolor placerat. Nulla ornare malesuada elit eu faucibus. Mauris ultricies tincidunt eleifend. Aliquam erat volutpat. Morbi nec ipsum sed sem facilisis tristique nec et ligula. Vivamus id feugiat sapien.
-
-
- Nam ac tincidunt arcu. Nam non metus sem. Morbi eleifend metus vel venenatis accumsan. Vestibulum pharetra, ante quis sollicitudin aliquam, orci ex pretium ipsum, congue rhoncus dui orci sed velit. Cras ac leo vel massa rutrum auctor eget sed orci. Aenean id nisi consectetur, dapibus tellus ut, finibus metus. Integer tristique est vitae lectus suscipit, ut vulputate est fringilla. Donec pharetra facilisis erat at venenatis. Etiam faucibus dignissim leo eget congue. Sed vehicula imperdiet neque id gravida. Proin volutpat tortor quis quam molestie, faucibus condimentum ante sagittis. Suspendisse sit amet luctus tellus. Suspendisse a sapien hendrerit eros dictum faucibus. Vivamus pretium vel sem faucibus tristique. Integer iaculis pellentesque nunc ac pellentesque.
-
-
- Integer mollis, urna eget sollicitudin laoreet, nunc elit facilisis urna, ut finibus est mi eu quam. Nam ac venenatis massa. Vestibulum suscipit ac ligula venenatis scelerisque. Fusce rutrum nulla lectus, sed dignissim ipsum faucibus sodales. Suspendisse id aliquet quam. Maecenas facilisis mauris dolor, id accumsan ex vehicula in. In nisl ligula, fringilla in enim nec, lobortis sollicitudin purus. Aliquam vehicula euismod enim quis finibus. Fusce ornare tortor malesuada, consequat magna quis, porta dolor. Donec quis nisl ac sem dictum semper quis vel turpis. Proin sed leo in ante rhoncus pellentesque a eu urna. Phasellus consequat lectus et hendrerit luctus. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
-
-
- Suspendisse vitae eros elit. Maecenas id urna tempus, tempus turpis id, blandit turpis. Fusce augue quam, pellentesque et suscipit consectetur, pharetra in mi. Suspendisse non ultricies purus. Integer dignissim condimentum sem ac porta. Vivamus viverra congue massa, vitae fermentum est scelerisque et. Duis a urna vitae odio semper dictum a sed nisl. In fringilla hendrerit massa, at luctus arcu tincidunt nec. Donec gravida, mauris sit amet porta lobortis, justo sem vehicula ipsum, at pretium sem leo sed libero. Morbi tristique rhoncus suscipit. Nullam at malesuada sapien. Nam in egestas tellus. Nulla quis metus dui. Suspendisse sit amet nisi at lectus ultricies egestas.
-
-
- Integer pulvinar felis sit amet dignissim fermentum. Nulla sodales enim mi, varius feugiat sapien congue eget. Morbi vitae ipsum non ligula eleifend molestie. Aenean bibendum tortor sapien, quis volutpat ante ultricies id. Morbi varius dolor ac ante posuere, sit amet tincidunt lectus pulvinar. Proin id efficitur neque. Nullam vel feugiat dui. Curabitur imperdiet lacinia eros, ac iaculis odio. Praesent quis pretium sapien, quis posuere lectus. Proin eleifend purus nec massa aliquam commodo. Quisque auctor suscipit ex sed tristique. Sed eget ultrices est. Suspendisse nunc justo, dapibus at eros ac, rutrum vestibulum est.
-
-
-
-
-
-`;
diff --git a/src/components/Modal/index.ts b/src/components/Modal/index.ts
index 2736f369c..48f35c717 100644
--- a/src/components/Modal/index.ts
+++ b/src/components/Modal/index.ts
@@ -1,2 +1 @@
export { Modal as default } from './Modal';
-export { Modal as ModalV2 } from './Modal-v2';
diff --git a/src/components/NumberIcon/NumberIcon-v2.module.css b/src/components/NumberIcon/NumberIcon-v2.module.css
deleted file mode 100644
index 9b996fe0b..000000000
--- a/src/components/NumberIcon/NumberIcon-v2.module.css
+++ /dev/null
@@ -1,90 +0,0 @@
-@import '../../design-tokens/mixins.css';
-/*------------------------------------*\
- # NUMBER ICON
-\*------------------------------------*/
-
-/**
- * Number Icon displays a number enclosed in a circle.
- *
- * Centers the number text in the circle.
- */
-.number-icon {
- /* Line height set to 1 here since this should only ever be on 1 line and it evens out padding in circle. */
- line-height: 1;
- display: flex;
- justify-content: center;
- align-items: center;
-
- /* The circle part of the icon, made with borders. */
- border: calc(var(--eds-border-width-sm) * 1px) solid;
- border-color: inherit;
- border-radius: calc(var(--eds-border-radius-full) * 1px);
-
- &.number-icon--is-interactive {
- cursor: pointer;
- }
-}
-
-/**
- * Size Variants.
- */
-.number-icon--size-md {
- height: 1.5rem;
- width: 1.5rem;
- min-width: 1.5rem;
-}
-
-.number-icon--size-lg {
- height: 2rem;
- width: 2rem;
- min-width: 2rem;
-}
-
-/* Colors & Theme */
-
-/**
- * Interactive States
- */
-.number-icon--status-default {
- color: var(--eds-theme-color-text-utility-interactive-primary);
- border-color: var(--eds-theme-color-border-utility-interactive);
- background-color: var(--eds-theme-color-background-utility-interactive-no-emphasis);
-
- &.number-icon--is-interactive {
- &:hover {
- border-color: var(--eds-theme-color-border-utility-interactive-hover);
- background-color: var(--eds-theme-color-background-utility-interactive-no-emphasis-hover);
- }
-
- &:active {
- border-color: var(--eds-theme-color-border-utility-interactive-active);
- background-color: var(--eds-theme-color-background-utility-interactive-no-emphasis-active);
- }
- }
-}
-
-.number-icon--status-completed {
- color: var(--eds-theme-color-text-utility-inverse);
- border-color: var(--eds-theme-color-background-utility-favorable-high-emphasis);
- background-color: var(--eds-theme-color-background-utility-favorable-high-emphasis);
-
- &.number-icon--is-interactive {
- &:hover {
- border-color: var(--eds-theme-color-background-utility-favorable-high-emphasis-hover);
- background-color: var(--eds-theme-color-background-utility-favorable-high-emphasis-hover);
- }
-
- &:active {
- border-color: var(--eds-theme-color-background-utility-favorable-high-emphasis-active);
- background-color: var(--eds-theme-color-background-utility-favorable-high-emphasis-active);
- }
- }
-}
-
-.number-icon--status-incomplete {
- color: var(--eds-theme-color-text-utility-default-secondary);
- border-color: var(--eds-theme-color-border-utility-default-medium-emphasis);
-
- border-style: dashed;
- pointer-events: none;
-}
\ No newline at end of file
diff --git a/src/components/NumberIcon/NumberIcon-v2.stories.tsx b/src/components/NumberIcon/NumberIcon-v2.stories.tsx
deleted file mode 100644
index fce1505a6..000000000
--- a/src/components/NumberIcon/NumberIcon-v2.stories.tsx
+++ /dev/null
@@ -1,126 +0,0 @@
-import type { StoryObj, Meta } from '@storybook/react';
-import React from 'react';
-
-import { NumberIcon } from './NumberIcon-v2';
-
-export default {
- title: 'Components/V2/NumberIcon',
- component: NumberIcon,
- parameters: {
- layout: 'centered',
- badges: ['intro-1.0', 'current-2.0'],
- },
- args: {
- 'aria-label': 'number icon example',
- number: 1,
- },
- decorators: [(Story) =>
- ),
-};
-
-/**
- * This Implementation example shows how to use Number Icon to build a stepper-like component.
- *
- * - incomplete rows are aligned with each number icon to show progress
- */
-export const NumberIconList: Story = {
- parameters: {
- badges: ['intro-1.0', 'current-2.0', 'implementationExample'],
- },
- render: () => (
-
-
-
-
-
-
-
-
- ),
-};
diff --git a/src/components/NumberIcon/NumberIcon-v2.tsx b/src/components/NumberIcon/NumberIcon-v2.tsx
deleted file mode 100644
index 03352224c..000000000
--- a/src/components/NumberIcon/NumberIcon-v2.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-import clsx from 'clsx';
-import React from 'react';
-
-import type { Size } from '../../util/variant-types';
-
-import Text from '../Text';
-import styles from './NumberIcon-v2.module.css';
-
-export interface Props {
- // Component API
- /**
- * (Required) Screen-reader text for the number icon.
- */
- 'aria-label': string;
- /**
- * CSS class names that can be appended to the component.
- */
- className?: string;
- // Design API
- /**
- * Whether `NumberIcon` can be focused on, clicked, etc.
- */
- isInteractive?: boolean;
- /**
- * Number to be shown as the icon. Maximum of two digits.
- */
- number?: number;
- /**
- * The size of the icon.
- *
- * **Default is `"lg"`**.
- */
- size?: Extract;
- /**
- * Indication of the status of the referenced item
- */
- status?: 'completed' | 'incomplete' | 'default';
-}
-
-/**
- * `import {NumberIcon} from "@chanzuckerberg/eds";`
- *
- * Treats a numeral as an icon by wrapping it in a container and adding color/spacing.
- *
- */
-export const NumberIcon = ({
- className,
- isInteractive = false,
- number,
- status = 'default',
- size = 'lg',
- ...other
-}: Props) => {
- const componentClassName = clsx(
- className,
- styles['number-icon'],
- isInteractive && styles['number-icon--is-interactive'],
- size && styles[`number-icon--size-${size}`],
- status && styles[`number-icon--status-${status}`],
- );
-
- return (
-
- {number}
-
- );
-};
-
-NumberIcon.displayName = 'NumberIcon';
diff --git a/src/components/NumberIcon/NumberIcon.module.css b/src/components/NumberIcon/NumberIcon.module.css
index b06811b12..99ccb69c2 100644
--- a/src/components/NumberIcon/NumberIcon.module.css
+++ b/src/components/NumberIcon/NumberIcon.module.css
@@ -19,42 +19,72 @@
border: calc(var(--eds-border-width-sm) * 1px) solid;
border-color: inherit;
border-radius: calc(var(--eds-border-radius-full) * 1px);
-}
-.number-icon--incomplete {
- border: none;
- fill: var(--number-icon-incomplete-fill, var(--eds-theme-color-icon-neutral-subtle));
+ &.number-icon--is-interactive {
+ cursor: pointer;
+ }
}
/**
* Size Variants.
*/
-.number-icon--sm {
- /* Line height set to 1 here since this should only ever be on 1 line and it evens out padding in circle. */
- line-height: 1;
- /* Height and width should be the same to make the icon a circle and not oblong. */
- height: calc(var(--eds-size-2-and-half) / 16 * 1rem);
- width: calc(var(--eds-size-2-and-half) / 16 * 1rem);
- min-width: calc(var(--eds-size-2-and-half) / 16 * 1rem);
-}
-
-.number-icon--lg {
+.number-icon--size-md {
height: calc(var(--eds-size-3) / 16 * 1rem);
width: calc(var(--eds-size-3) / 16 * 1rem);
min-width: calc(var(--eds-size-3) / 16 * 1rem);
}
+.number-icon--size-lg {
+ height: calc(var(--eds-size-4) / 16 * 1rem);
+ width: calc(var(--eds-size-4) / 16 * 1rem);
+ min-width: calc(var(--eds-size-4) / 16 * 1rem);
+}
+
+/* Colors & Theme */
+
/**
- * Color Variants.
+ * Interactive States
*/
-.number-icon--base {
- color: var(--eds-theme-color-text-neutral-default);
+.number-icon--status-default {
+ color: var(--eds-theme-color-text-utility-interactive-primary);
+ border-color: var(--eds-theme-color-border-utility-interactive);
+ background-color: var(--eds-theme-color-background-utility-interactive-no-emphasis);
+
+ &.number-icon--is-interactive {
+ &:hover {
+ border-color: var(--eds-theme-color-border-utility-interactive-hover);
+ background-color: var(--eds-theme-color-background-utility-interactive-no-emphasis-hover);
+ }
- background-color: transparent;
+ &:active {
+ border-color: var(--eds-theme-color-border-utility-interactive-active);
+ background-color: var(--eds-theme-color-background-utility-interactive-no-emphasis-active);
+ }
+ }
}
-.number-icon--success {
- color: var(--eds-theme-color-text-neutral-default-inverse);
- background-color: var(--eds-theme-color-icon-utility-success);
- border-color: var(--eds-theme-color-icon-utility-success);
+.number-icon--status-completed {
+ color: var(--eds-theme-color-text-utility-inverse);
+ border-color: var(--eds-theme-color-background-utility-favorable-high-emphasis);
+ background-color: var(--eds-theme-color-background-utility-favorable-high-emphasis);
+
+ &.number-icon--is-interactive {
+ &:hover {
+ border-color: var(--eds-theme-color-background-utility-favorable-high-emphasis-hover);
+ background-color: var(--eds-theme-color-background-utility-favorable-high-emphasis-hover);
+ }
+
+ &:active {
+ border-color: var(--eds-theme-color-background-utility-favorable-high-emphasis-active);
+ background-color: var(--eds-theme-color-background-utility-favorable-high-emphasis-active);
+ }
+ }
}
+
+.number-icon--status-incomplete {
+ color: var(--eds-theme-color-text-utility-default-secondary);
+ border-color: var(--eds-theme-color-border-utility-default-medium-emphasis);
+
+ border-style: dashed;
+ pointer-events: none;
+}
\ No newline at end of file
diff --git a/src/components/NumberIcon/NumberIcon.stories.tsx b/src/components/NumberIcon/NumberIcon.stories.tsx
index 1294826fd..80100e18e 100644
--- a/src/components/NumberIcon/NumberIcon.stories.tsx
+++ b/src/components/NumberIcon/NumberIcon.stories.tsx
@@ -8,10 +8,10 @@ export default {
component: NumberIcon,
parameters: {
layout: 'centered',
- badges: ['intro-1.0'],
+ badges: ['intro-1.0', 'current-2.0'],
},
args: {
- 'aria-label': 'Step 1',
+ 'aria-label': 'number icon example',
number: 1,
},
decorators: [(Story) =>
,
+ ],
};
-export const Success: Story = {
+/**
+ * `NumberIcon` can be used in interactive contexts, when wrapped by a navigable or interactive element.
+ */
+export const IsInteractive: Story = {
args: {
- variant: 'success',
+ ...Sizes.args,
+ isInteractive: true,
},
+ render: Sizes.render,
};
-export const SuccessSmall: Story = {
+export const Completed: Story = {
args: {
- size: 'sm',
- variant: 'success',
+ ...Sizes.args,
+ status: 'completed',
},
+ render: Sizes.render,
+ decorators: Sizes.decorators,
};
-/**
- * When `incomplete` is defined and there is a `numberIconTitle` on the circle icon, then this will render
- * the proper icon with the incomplete text provided foro that component instance.
- */
export const Incomplete: Story = {
args: {
- size: 'sm',
- incomplete: true,
- number: undefined,
- numberIconTitle: 'Incomplete',
+ ...Sizes.args,
+ status: 'incomplete',
},
+ render: Sizes.render,
+ decorators: Sizes.decorators,
};
/**
@@ -97,40 +111,16 @@ export const DifferentNumbers: Story = {
*/
export const NumberIconList: Story = {
parameters: {
- badges: ['intro-1.0', 'implementationExample'],
+ badges: ['intro-1.0', 'current-2.0', 'implementationExample'],
},
render: () => (
-
-
-
-
-
-
+
+
+
+
+
+
),
};
diff --git a/src/components/NumberIcon/NumberIcon.test.ts b/src/components/NumberIcon/NumberIcon.test.ts
deleted file mode 100644
index ceb1888a9..000000000
--- a/src/components/NumberIcon/NumberIcon.test.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { generateSnapshots } from '@chanzuckerberg/story-utils';
-import type { StoryFile } from '@storybook/testing-react';
-import * as stories from './NumberIcon.stories';
-
-describe('', () => {
- generateSnapshots(stories as StoryFile);
-});
diff --git a/src/components/NumberIcon/NumberIcon.tsx b/src/components/NumberIcon/NumberIcon.tsx
index 9a2bf903b..9a82d8ea9 100644
--- a/src/components/NumberIcon/NumberIcon.tsx
+++ b/src/components/NumberIcon/NumberIcon.tsx
@@ -3,30 +3,27 @@ import React from 'react';
import type { Size } from '../../util/variant-types';
-import Icon, { type IconName } from '../Icon';
import Text from '../Text';
+
import styles from './NumberIcon.module.css';
export interface Props {
+ // Component API
/**
- * Screen-reader text for the number icon.
+ * (Required) Screen-reader text for the number icon.
*/
'aria-label': string;
/**
* CSS class names that can be appended to the component.
*/
className?: string;
+ // Design API
/**
- * Icon override for component.
- */
- icon?: Extract;
-
- /**
- * Incomplete prop to show incomplete state
+ * Whether `NumberIcon` can be focused on, clicked, etc.
*/
- incomplete?: boolean;
+ isInteractive?: boolean;
/**
- * Number to be shown as the icon.
+ * Number to be shown as the icon. Maximum of two digits.
*/
number?: number;
/**
@@ -34,21 +31,11 @@ export interface Props {
*
* **Default is `"lg"`**.
*/
- size?: Extract;
+ size?: Extract;
/**
- * The variant of the icon.
- *
- * **Default is `"base"`**.
- *
- * Variant `success` will use a symbol to mark success, along with different color tokens
+ * Indication of the status of the referenced item
*/
- variant?: 'base' | 'success';
- /**
- * Number icon title
- *
- * When using `incomplete` this is required.
- */
- numberIconTitle?: string;
+ status?: 'completed' | 'incomplete' | 'default';
}
/**
@@ -59,45 +46,30 @@ export interface Props {
*/
export const NumberIcon = ({
className,
+ isInteractive = false,
number,
+ status = 'default',
size = 'lg',
- variant = 'base',
- icon = 'circle',
- incomplete,
- numberIconTitle,
...other
}: Props) => {
const componentClassName = clsx(
- styles['number-icon'],
- styles[`number-icon--${variant}`],
- size && styles[`number-icon--${size}`],
- incomplete && styles['number-icon--incomplete'],
className,
+ styles['number-icon'],
+ isInteractive && styles['number-icon--is-interactive'],
+ size && styles[`number-icon--size-${size}`],
+ status && styles[`number-icon--status-${status}`],
);
return (
- {incomplete && numberIconTitle ? (
-
- ) : (
- /**
- * If this is not incomplete, then the number prop provided will show within the border.
- */
- number
- )}
+ {number}
);
};
diff --git a/src/components/NumberIcon/__snapshots__/NumberIcon.test.ts.snap b/src/components/NumberIcon/__snapshots__/NumberIcon.test.ts.snap
deleted file mode 100644
index fa9f3b034..000000000
--- a/src/components/NumberIcon/__snapshots__/NumberIcon.test.ts.snap
+++ /dev/null
@@ -1,346 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[` Default story renders snapshot 1`] = `
-
- );
- },
-};
diff --git a/src/components/Radio/Radio-v2.test.tsx b/src/components/Radio/Radio-v2.test.tsx
deleted file mode 100644
index 4fd5b148f..000000000
--- a/src/components/Radio/Radio-v2.test.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import { generateSnapshots } from '@chanzuckerberg/story-utils';
-import type { StoryFile } from '@storybook/testing-react';
-import { render, screen } from '@testing-library/react';
-import userEvent from '@testing-library/user-event';
-import React from 'react';
-import { Radio } from './Radio-v2';
-import * as stories from './Radio-v2.stories';
-
-describe(' (v2)', () => {
- generateSnapshots(stories as StoryFile);
-
- test('should toggle the radio with space', async () => {
- const user = userEvent.setup();
- const onChange = jest.fn();
-
- function ControlledRadio() {
- const [checked, setChecked] = React.useState(false);
- const handleChange = () => {
- setChecked(!checked);
- onChange();
- };
-
- return (
-
- );
- }
-
- render();
- const radio = screen.getByRole('radio');
- radio.focus();
-
- await user.keyboard(' ');
-
- expect(radio).toBeChecked();
- expect(onChange).toHaveBeenCalledTimes(1);
- });
-});
diff --git a/src/components/Radio/Radio-v2.tsx b/src/components/Radio/Radio-v2.tsx
deleted file mode 100644
index e826f7d47..000000000
--- a/src/components/Radio/Radio-v2.tsx
+++ /dev/null
@@ -1,162 +0,0 @@
-import clsx from 'clsx';
-import React from 'react';
-import type { ReactNode } from 'react';
-
-import { useId } from '../../util/useId';
-import type { EitherInclusive } from '../../util/utility-types';
-
-import FieldLabel from '../FieldLabel';
-import Text from '../Text';
-
-import styles from './Radio-v2.module.css';
-
-type RadioHTMLElementProps = Omit<
- React.InputHTMLAttributes,
- 'checked' | 'id' | 'name'
->;
-
-type RadioInputProps = RadioHTMLElementProps & {
- /**
- * Whether checkbox is checked.
- */
- checked?: boolean;
- /**
- * Additional classnames passed in for styling.
- */
- className?: string;
- /**
- * Radio ID. Used to connect the input with a label for accessibility purposes.
- */
- id?: string;
- /**
- * Whether the radio button is in an error state
- */
- isError?: boolean;
-};
-
-type RadioProps = RadioInputProps & {
- // Component API
- /**
- * HTML id attribute. If not passed, this component
- * will generate an id to use for accessibility.
- */
- id?: string;
- /**
- * The field name to use in a form
- */
- name?: string;
- // Design API
- /**
- * Whether the radio button is in an error state
- */
- isError?: boolean;
- /**
- * Additional descriptive text below the primary label, adding additional detail
- */
- subLabel?: string;
-} & EitherInclusive<
- {
- /**
- * Visible text label for the component.
- */
- label: ReactNode;
- },
- {
- /**
- * Aria-label to provide an accesible name for the text input if no visible label is provided.
- */
- 'aria-label': string;
- }
- >;
-
-/**
- * Radio input element, exported for greater flexibility.
- * You must provide an `id` prop and connect it to a visible label.
- */
-const RadioInput = ({
- checked,
- className,
- disabled,
- isError,
- ...other
-}: RadioInputProps) => {
- return (
-
-
-
-
-
- );
-};
-
-/**
- * `import {Radio} from "@chanzuckerberg/eds";`
- *
- * Radio control indicating if one item is selected or unselected from a set of other options. Uncontrolled by default, it can be used in place of a select field in form data.
- *
- * NOTE: This component requires `label` or `aria-label` prop
- */
-export const Radio = ({
- className,
- disabled,
- label,
- id,
- isError = false,
- subLabel,
- ...other
-}: RadioProps) => {
- const generatedId = useId();
- const radioId = id || generatedId;
-
- const componentClassName = clsx(
- styles['radio'],
- isError && styles['radio--error'],
- className,
- );
-
- return (
-