Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(VisualPageIndicator): introduce 1.0 component #2118

Merged
merged 2 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading