diff --git a/src/components/VisualPageIndicator/VisualPageIndicator.module.css b/src/components/VisualPageIndicator/VisualPageIndicator.module.css new file mode 100644 index 000000000..91ef2c959 --- /dev/null +++ b/src/components/VisualPageIndicator/VisualPageIndicator.module.css @@ -0,0 +1,33 @@ +/*------------------------------------*\ + # VISUAL PAGE INDICATOR +\*------------------------------------*/ + +/** + * VisualPageIndicator + */ +.visual-page-indicator { + display: flex; + justify-content: center; + gap: calc(var(--eds-size-1-and-half) / 16 * 1rem); +} + +.visual-page-indicator__item { + --visual-page-indicator-bg: var(--eds-theme-color-background-utility-disabled-medium-emphasis); + + height: calc(var(--eds-size-1-and-half) / 16 * 1rem); + width: calc(var(--eds-size-1-and-half) / 16 * 1rem); + border-radius: calc(var(--eds-border-radius-full) * 1px); + + background-color: var(--visual-page-indicator-bg); +} + +.visual-page-indicator--active { + --visual-page-indicator-bg: var(--eds-theme-color-background-brand-pink); +} + +/** + * TODO-AH: + * - Add color ease with eds-anim-move-medium / eds-anim-ease + * TODO-AH: + * - introduce new tokens for inactive and active? (or use the root tokens?) + */ \ No newline at end of file diff --git a/src/components/VisualPageIndicator/VisualPageIndicator.stories.ts b/src/components/VisualPageIndicator/VisualPageIndicator.stories.ts new file mode 100644 index 000000000..507602276 --- /dev/null +++ b/src/components/VisualPageIndicator/VisualPageIndicator.stories.ts @@ -0,0 +1,37 @@ +import type { StoryObj, Meta } from '@storybook/react'; +import type React from 'react'; + +import { VisualPageIndicator } from './VisualPageIndicator'; + +export default { + title: 'Components/VisualPageIndicator', + component: VisualPageIndicator, + parameters: { + badges: ['api-1.0', 'theme-2.0'], + }, +} as Meta; + +type Args = React.ComponentProps; + +export const Default: StoryObj = { + args: { + activePage: 0, + totalPageCount: 6, + }, +}; + +export const MinimumPages: StoryObj = { + args: { + activePage: 1, + totalPageCount: 2, + }, +}; + +export const FivePages: StoryObj = { + args: { + activePage: 2, + totalPageCount: 5, + }, +}; + +// TODO-AH: add implementation example showing usage of state with a label for a11y handling diff --git a/src/components/VisualPageIndicator/VisualPageIndicator.test.ts b/src/components/VisualPageIndicator/VisualPageIndicator.test.ts new file mode 100644 index 000000000..aa80c4b1a --- /dev/null +++ b/src/components/VisualPageIndicator/VisualPageIndicator.test.ts @@ -0,0 +1,7 @@ +import { generateSnapshots } from '@chanzuckerberg/story-utils'; +import * as stories from './VisualPageIndicator.stories'; +import type { StoryFile } from '../../util/utility-types'; + +describe('', () => { + generateSnapshots(stories as StoryFile); +}); diff --git a/src/components/VisualPageIndicator/VisualPageIndicator.tsx b/src/components/VisualPageIndicator/VisualPageIndicator.tsx new file mode 100644 index 000000000..8580dd634 --- /dev/null +++ b/src/components/VisualPageIndicator/VisualPageIndicator.tsx @@ -0,0 +1,59 @@ +import clsx from 'clsx'; +import React from 'react'; +import styles from './VisualPageIndicator.module.css'; + +export type VisualPageIndicatorProps = { + // Component API + /** + * CSS class names that can be appended to the component. + */ + className?: string; + // Design API + /** + * Index of the active page in the indicator (0-based). + */ + activePage: number; + /** + * Total number of pages available in this experience + */ + totalPageCount: number; +}; + +/** + * `import {VisualPageIndicator} from "@chanzuckerberg/eds";` + * + * Static visual cue to help users understand their current position within a series of content or pages. + */ +export const VisualPageIndicator = ({ + className, + activePage = 0, + totalPageCount = 0, + ...other +}: VisualPageIndicatorProps) => { + const componentClassName = clsx(styles['visual-page-indicator'], className); + + // TODO-AH: add warning when totalPageCount < 2 + // TODO-AH: add error for activePage under and overflow, with clamping + // TODO-AH: handle warning when not used in conjunction with an a descriptive label + + return ( +
    + {Array(totalPageCount) + .fill(0) + .map((_, index) => { + return `Page ${index}`; + }) + .map((name, index) => { + return ( +
  • + ); + })} +
+ ); +}; diff --git a/src/components/VisualPageIndicator/__snapshots__/VisualPageIndicator.test.ts.snap b/src/components/VisualPageIndicator/__snapshots__/VisualPageIndicator.test.ts.snap new file mode 100644 index 000000000..87badf226 --- /dev/null +++ b/src/components/VisualPageIndicator/__snapshots__/VisualPageIndicator.test.ts.snap @@ -0,0 +1,61 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` Default story renders snapshot 1`] = ` +
    +
  • +
  • +
  • +
  • +
  • +
  • +
+`; + +exports[` FivePages story renders snapshot 1`] = ` +
    +
  • +
  • +
  • +
  • +
  • +
+`; + +exports[` MinimumPages story renders snapshot 1`] = ` +
    +
  • +
  • +
+`; diff --git a/src/components/VisualPageIndicator/index.ts b/src/components/VisualPageIndicator/index.ts new file mode 100644 index 000000000..fcf19fad2 --- /dev/null +++ b/src/components/VisualPageIndicator/index.ts @@ -0,0 +1 @@ +export { VisualPageIndicator as default } from './VisualPageIndicator'; diff --git a/src/index.ts b/src/index.ts index f0bb233e6..d305e50b4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -78,3 +78,4 @@ export type { AppNotificationProps as AppNotificationV2Props } from './component */ // https://headlessui.com/v1/react/transition export { Transition } from '@headlessui/react'; +export { default as VisualPageIndicator } from './components/VisualPageIndicator';