Skip to content

Commit

Permalink
feat(VisualPageIndicator): introduce 1.0 component (#2118)
Browse files Browse the repository at this point in the history
- add in new token eds-theme-color-background-visual-page-indicator-current
- add in new token eds-theme-color-background-visual-page-indicator
- add in component VisualPageIndicator with tests/snapshots
- add in dynamic usage assertions
- add in reduce motion support
  • Loading branch information
booc0mtaco authored Dec 18, 2024
1 parent 032cb5d commit 3df98a3
Show file tree
Hide file tree
Showing 12 changed files with 277 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .storybook/data/tokens.json
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@
"eds-theme-color-background-table-row-stripe-1": "#F5FAFF",
"eds-theme-color-background-table-row-stripe-2": "rgb(var(--eds-color-white) / 1)",
"eds-theme-color-background-table-row-selected": "#CEE6FF",
"eds-theme-color-background-visual-page-indicator": "#CFC9C7",
"eds-theme-color-background-visual-page-indicator-current": "#DB458D",
"eds-theme-color-background-utility-base-1": "rgb(var(--eds-color-white) / 1)",
"eds-theme-color-background-utility-base-2": "#FDF9F8",
"eds-theme-color-background-utility-container": "rgb(var(--eds-color-white) / 1)",
Expand Down
34 changes: 34 additions & 0 deletions src/components/VisualPageIndicator/VisualPageIndicator.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*------------------------------------*\
# 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-visual-page-indicator);

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);

transition: background-color ease calc(var(--eds-anim-move-medium) * 1s);

background-color: var(--visual-page-indicator-bg);
}

.visual-page-indicator--active {
--visual-page-indicator-bg: var(--eds-theme-color-background-visual-page-indicator-current);
}

@media screen and (prefers-reduced-motion: reduce) {
.visual-page-indicator__item {
transition: none;
}
}
37 changes: 37 additions & 0 deletions src/components/VisualPageIndicator/VisualPageIndicator.stories.ts
Original file line number Diff line number Diff line change
@@ -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<Args>;

type Args = React.ComponentProps<typeof VisualPageIndicator>;

export const Default: StoryObj<Args> = {
args: {
activePage: 0,
totalPageCount: 6,
},
};

export const MinimumPages: StoryObj<Args> = {
args: {
activePage: 1,
totalPageCount: 2,
},
};

export const FivePages: StoryObj<Args> = {
args: {
activePage: 2,
totalPageCount: 5,
},
};

// TODO: add implementation example showing usage of state with a label for a11y handling
55 changes: 55 additions & 0 deletions src/components/VisualPageIndicator/VisualPageIndicator.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { generateSnapshots } from '@chanzuckerberg/story-utils';
import { render } from '@testing-library/react';

import React from 'react';
import { VisualPageIndicator } from './VisualPageIndicator';

import * as stories from './VisualPageIndicator.stories';
import type { StoryFile } from '../../util/utility-types';

describe('<VisualPageIndicator />', () => {
beforeEach(() => {
// Add in mocks for the calls that can occur in implementation to suppress logging in tests
const consoleMock = jest.spyOn(console, 'error');
const consoleWarnMock = jest.spyOn(console, 'warn');
consoleMock.mockImplementation();
consoleWarnMock.mockImplementation();
});

afterEach(() => {
jest.resetAllMocks();
});

generateSnapshots(stories as StoryFile);

describe('emits messages when misused', () => {
let consoleErrorMock: jest.SpyInstance, consoleWarnMock: jest.SpyInstance;
beforeEach(() => {
consoleWarnMock = jest.spyOn(console, 'warn');
consoleErrorMock = jest.spyOn(console, 'error');
consoleWarnMock.mockImplementation();
consoleErrorMock.mockImplementation();
});

it('errors when active page is above range', () => {
render(<VisualPageIndicator activePage={3} totalPageCount={2} />);

expect(consoleWarnMock).toHaveBeenCalledTimes(0);
expect(consoleErrorMock).toHaveBeenCalledTimes(1);
});

it('errors when active page is below range', () => {
render(<VisualPageIndicator activePage={-1} totalPageCount={2} />);

expect(consoleWarnMock).toHaveBeenCalledTimes(0);
expect(consoleErrorMock).toHaveBeenCalledTimes(1);
});

it('warns when total page count is too small', () => {
render(<VisualPageIndicator activePage={0} totalPageCount={1} />);

expect(consoleWarnMock).toHaveBeenCalledTimes(1);
expect(consoleErrorMock).toHaveBeenCalledTimes(0);
});
});
});
68 changes: 68 additions & 0 deletions src/components/VisualPageIndicator/VisualPageIndicator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import clsx from 'clsx';
import React from 'react';
import { assertEdsUsage } from '../../util/logging';

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,
totalPageCount,
...other
}: VisualPageIndicatorProps) => {
const componentClassName = clsx(styles['visual-page-indicator'], className);

assertEdsUsage(
[totalPageCount < 2],
'The minimum allowed count of indicators is 2',
);

assertEdsUsage(
[activePage < 0, activePage > totalPageCount - 1],
`The position in the indicator is out of range: [0, ${totalPageCount - 1}]`,
'error',
);

return (
<ul className={componentClassName} {...other}>
{Array(totalPageCount)
.fill(0)
.map((_, index) => {
return `Page ${index}`;
})
.map((name, index) => {
return (
<li
className={clsx(
styles['visual-page-indicator__item'],
index === activePage && styles['visual-page-indicator--active'],
)}
key={name}
></li>
);
})}
</ul>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`<VisualPageIndicator /> Default story renders snapshot 1`] = `
<ul
class="visual-page-indicator"
>
<li
class="visual-page-indicator__item visual-page-indicator--active"
/>
<li
class="visual-page-indicator__item"
/>
<li
class="visual-page-indicator__item"
/>
<li
class="visual-page-indicator__item"
/>
<li
class="visual-page-indicator__item"
/>
<li
class="visual-page-indicator__item"
/>
</ul>
`;

exports[`<VisualPageIndicator /> FivePages story renders snapshot 1`] = `
<ul
class="visual-page-indicator"
>
<li
class="visual-page-indicator__item"
/>
<li
class="visual-page-indicator__item"
/>
<li
class="visual-page-indicator__item visual-page-indicator--active"
/>
<li
class="visual-page-indicator__item"
/>
<li
class="visual-page-indicator__item"
/>
</ul>
`;

exports[`<VisualPageIndicator /> MinimumPages story renders snapshot 1`] = `
<ul
class="visual-page-indicator"
>
<li
class="visual-page-indicator__item"
/>
<li
class="visual-page-indicator__item visual-page-indicator--active"
/>
</ul>
`;
1 change: 1 addition & 0 deletions src/components/VisualPageIndicator/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { VisualPageIndicator as default } from './VisualPageIndicator';
10 changes: 10 additions & 0 deletions src/design-tokens/themes.json
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,16 @@
"value": "{eds.color.blue.100}"
}
},
"visualPageIndicator": {
"@": {
"value": "{eds.color.neutral.200}",
"group": "color"
},
"current": {
"value": "{eds.theme.color.background.brand.pink}",
"group": "color"
}
},
"utility": {
"base": {
"1": {
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
2 changes: 2 additions & 0 deletions src/tokens-dist/css/variables.css
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,7 @@
--eds-theme-color-background-utility-container-active: var(--eds-color-neutral-100);
--eds-theme-color-background-utility-container-hover: var(--eds-color-neutral-050);
--eds-theme-color-background-utility-base-2: var(--eds-color-neutral-025);
--eds-theme-color-background-visual-page-indicator: var(--eds-color-neutral-200);
--eds-theme-color-background-table-row-selected: var(--eds-color-blue-100);
--eds-theme-color-background-table-row-stripe-2: var(--eds-theme-color-background-utility-base-1);
--eds-theme-color-background-table-row-stripe-1: var(--eds-color-blue-025);
Expand Down Expand Up @@ -826,4 +827,5 @@
--eds-theme-color-icon-utility-default-primary-active: var(--eds-theme-color-text-utility-default-primary-active);
--eds-theme-color-icon-utility-default-primary-hover: var(--eds-theme-color-text-utility-default-primary-hover);
--eds-theme-color-icon-utility-default-primary: var(--eds-theme-color-text-utility-default-primary);
--eds-theme-color-background-visual-page-indicator-current: var(--eds-theme-color-background-brand-pink);
}
4 changes: 4 additions & 0 deletions src/tokens-dist/json/variables-nested.json
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,10 @@
},
"selected": "#CEE6FF"
},
"visualPageIndicator": {
"@": "#CFC9C7",
"current": "#DB458D"
},
"utility": {
"base": {
"1": "rgb(var(--eds-color-white) / 1)",
Expand Down
2 changes: 2 additions & 0 deletions src/tokens-dist/ts/colors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export const EdsThemeColorBackgroundTableRowStripe1 = '#F5FAFF';
export const EdsThemeColorBackgroundTableRowStripe2 =
'rgb(var(--eds-color-white) / 1)';
export const EdsThemeColorBackgroundTableRowSelected = '#CEE6FF';
export const EdsThemeColorBackgroundVisualPageIndicator = '#CFC9C7';
export const EdsThemeColorBackgroundVisualPageIndicatorCurrent = '#DB458D';
export const EdsThemeColorBackgroundUtilityBase1 =
'rgb(var(--eds-color-white) / 1)';
export const EdsThemeColorBackgroundUtilityBase2 = '#FDF9F8';
Expand Down

0 comments on commit 3df98a3

Please sign in to comment.