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(Timer): create component #1130

Merged
merged 26 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
2bf6f62
first poc
marcoskolodny May 24, 2024
e4a592c
fix wrap
marcoskolodny May 27, 2024
cd8d1e0
fix styles and code cleanup
marcoskolodny May 27, 2024
c7be912
update component logic and clean up code
marcoskolodny May 27, 2024
5e40431
translate tokens
marcoskolodny May 28, 2024
bbe939c
refactor code
marcoskolodny May 28, 2024
05b5300
add aria label
marcoskolodny May 28, 2024
0ad130d
refactor logic
marcoskolodny May 28, 2024
7c0bd27
add display timers
marcoskolodny May 28, 2024
43cf9e7
add snippets
marcoskolodny May 28, 2024
e116c57
fix ssr test and add acceptance check
marcoskolodny May 28, 2024
0f7ea42
add timer role
marcoskolodny May 28, 2024
386f16b
add timer story
marcoskolodny May 28, 2024
2752083
Merge branch 'master' of github.com:Telefonica/mistica-web into WEB-1…
marcoskolodny May 28, 2024
a8136d5
renaming, boxed as props and unit tests
marcoskolodny May 29, 2024
201d05d
force time 0 in acceptance tests and add screenshot tests
marcoskolodny May 29, 2024
2644b74
add ssr test
marcoskolodny May 29, 2024
ca276db
add aria label logic
marcoskolodny May 29, 2024
6bc4f7c
update logic of text timer
marcoskolodny May 29, 2024
8565762
cleanup code
marcoskolodny May 29, 2024
ba65e8a
fix text wrapping in short label
marcoskolodny May 30, 2024
9a1b34a
resolve conflicts
marcoskolodny May 30, 2024
7f6de36
resolve comments
marcoskolodny May 31, 2024
4106849
fix text wrapping with long label and text decoration in link
marcoskolodny May 31, 2024
c313188
design updates
marcoskolodny Jun 4, 2024
92cb931
fix ssr test
marcoskolodny Jun 4, 2024
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
62 changes: 62 additions & 0 deletions playroom/snippets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2833,11 +2833,73 @@ const ProgressBlockSnippets = [
},
];

const timerSnippets: Array<Snippet> = [
{
group: 'TextTimer',
name: 'No Label',
code: `
<TextTimer
endTimestamp={Date.now() + 1000 * 60 * 60 * 24}
minTimeUnit="seconds"
maxTimeUnit="days"
/>
`,
},
{
group: 'TextTimer',
name: 'Short Label',
code: `
<TextTimer
endTimestamp={Date.now() + 1000 * 60 * 60 * 24}
minTimeUnit="seconds"
maxTimeUnit="days"
labelType="short"
marcoskolodny marked this conversation as resolved.
Show resolved Hide resolved
/>
`,
},
{
group: 'TextTimer',
name: 'Long Label',
code: `
<TextTimer
endTimestamp={Date.now() + 1000 * 60 * 60 * 24}
minTimeUnit="seconds"
maxTimeUnit="days"
labelType="long"
/>
`,
},
{
group: 'Timer',
name: 'default',
code: `
<Timer
endTimestamp={Date.now() + 1000 * 60 * 60 * 24}
minTimeUnit="seconds"
maxTimeUnit="days"
/>
`,
},
{
group: 'Timer',
name: 'boxed',
code: `
<Timer
endTimestamp={Date.now() + 1000 * 60 * 60 * 24}
minTimeUnit="seconds"
maxTimeUnit="days"
boxed
/>
`,
},
];

export default [
...buttonSnippets,
...formSnippets,
...feedbackSnippets,
...skeletonSnippets,
...timerSnippets,
{group: 'Feedbacks', name: 'Snackbar', code: '<Snackbar message="Some message here" />'},
...layoutSnippets,
{
Expand Down
6 changes: 6 additions & 0 deletions src/__acceptance_tests__/__ssr_pages__/timer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import * as React from 'react';
import {Timer} from '../../..';

const TimerTest = (): JSX.Element => <Timer endTimestamp={0} />;

export default TimerTest;
5 changes: 5 additions & 0 deletions src/__acceptance_tests__/timer-ssr-acceptance-test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import {openSSRPage} from '../test-utils';

test('ssr timer', async () => {
await openSSRPage({name: 'timer'});
});
7 changes: 7 additions & 0 deletions src/__private_stories__/skin-components-story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
Title3,
IconButton,
Hero,
Timer,
} from '..';
import {InternalIconButton} from '../icon-button';
import avatarImg from '../__stories__/images/avatar.jpg';
Expand Down Expand Up @@ -396,6 +397,12 @@ export const Default: StoryComponent<Args> = ({variant}) => {
button={<ButtonPrimary onPress={() => {}}>Action</ButtonPrimary>}
buttonLink={<ButtonLink onPress={() => {}}>Link</ButtonLink>}
/>

{/** Timer */}
<Inline space={16}>
<Timer endTimestamp={0} maxTimeUnit="days" />
<Timer endTimestamp={0} maxTimeUnit="days" boxed />
</Inline>
</Stack>
</Box>
</ResponsiveLayout>
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
marcoskolodny marked this conversation as resolved.
Show resolved Hide resolved
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
85 changes: 85 additions & 0 deletions src/__screenshot_tests__/timer-screenshot-test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import {openStoryPage, screen, setRootFontSize} from '../test-utils';

const DEVICE = ['DESKTOP', 'MOBILE_IOS'] as const;

test.each`
minTimeUnit | maxTimeUnit
${undefined} | ${undefined}
${'minutes'} | ${undefined}
${undefined} | ${'days'}
${'minutes'} | ${'days'}
${'seconds'} | ${'seconds'}
`(
'TextTimer - minTimeUnit = $minTimeUnit, maxTimeUnit = $maxTimeUnit',
async ({minTimeUnit, maxTimeUnit}) => {
await openStoryPage({
id: 'components-timer--text-timer-story',
args: {minTimeUnit, maxTimeUnit},
});

const timer = await screen.findByTestId('timer');

const image = await timer.screenshot();
expect(image).toMatchImageSnapshot();
}
);

test.each`
minTimeUnit | maxTimeUnit
${undefined} | ${undefined}
${'minutes'} | ${undefined}
${undefined} | ${'days'}
${'minutes'} | ${'days'}
${'seconds'} | ${'seconds'}
`('Timer - minTimeUnit = $minTimeUnit, maxTimeUnit = $maxTimeUnit', async ({minTimeUnit, maxTimeUnit}) => {
await openStoryPage({
id: 'components-timer--timer-story',
args: {minTimeUnit, maxTimeUnit},
});

const timer = await screen.findByTestId('timer');

const image = await timer.screenshot();
expect(image).toMatchImageSnapshot();
});

test.each(DEVICE)('Timer - boxed (%s)', async (device) => {
await openStoryPage({
id: 'components-timer--timer-story',
device,
args: {minTimeUnit: 'seconds', maxTimeUnit: 'days', boxed: true},
});

const timer = await screen.findByTestId('timer');

const image = await timer.screenshot();
expect(image).toMatchImageSnapshot();
});

test('Timer - boxed with big fontSize', async () => {
await openStoryPage({
id: 'components-timer--timer-story',
args: {minTimeUnit: 'seconds', maxTimeUnit: 'days', boxed: true},
});

await setRootFontSize(32);

const timer = await screen.findByTestId('timer');

const image = await timer.screenshot();
expect(image).toMatchImageSnapshot();
});

test('Timer - wraps if needed', async () => {
await openStoryPage({
id: 'components-timer--timer-story',
args: {minTimeUnit: 'seconds', maxTimeUnit: 'days', boxed: true},
});

await setRootFontSize(72);

const timer = await screen.findByTestId('timer');

const image = await timer.screenshot();
expect(image).toMatchImageSnapshot();
});
178 changes: 178 additions & 0 deletions src/__stories__/timer-story.tsx
marcoskolodny marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import * as React from 'react';
import {ResponsiveLayout, Box, Text3, Timer, TextTimer, Stack, Title1, Text2} from '..';
import {isEqual} from '../utils/helpers';

import type {RemainingTime, TimeUnit} from '../timer';
import type {Variant} from '../theme-variant-context';

export default {
title: 'Components/Timer',
parameters: {fullScreen: true},
};

interface BaseArgs {
themeVariant: Variant;
minTimeUnit: TimeUnit | 'undefined';
maxTimeUnit: TimeUnit | 'undefined';
days: number;
hours: number;
minutes: number;
seconds: number;
}

const SECOND = 1000;
const MINUTE = SECOND * 60;
const HOUR = MINUTE * 60;
const DAY = HOUR * 24;

const baseArgs: BaseArgs = {
themeVariant: 'default',
minTimeUnit: 'undefined',
maxTimeUnit: 'undefined',
days: 1,
hours: 0,
minutes: 0,
seconds: 0,
};

const baseArgTypes = {
themeVariant: {
options: ['default', 'inverse', 'alternative'],
control: {type: 'select'},
},
minTimeUnit: {
options: ['undefined', 'seconds', 'minutes', 'hours', 'days'],
control: {type: 'select'},
},
maxTimeUnit: {
options: ['undefined', 'seconds', 'minutes', 'hours', 'days'],
control: {type: 'select'},
},
};

type TextTimerArgs = BaseArgs & {labelType: 'none' | 'short' | 'long'};

export const TextTimerStory: StoryComponent<TextTimerArgs> = ({
labelType,
themeVariant,
minTimeUnit,
maxTimeUnit,
days,
hours,
minutes,
seconds,
}) => {
const [remainingTime, setRemainingTime] = React.useState<RemainingTime>();
const [endTimestamp, setEndTimestamp] = React.useState(
Date.now() + DAY * days + HOUR * hours + MINUTE * minutes + SECOND * seconds
);

React.useEffect(() => {
setEndTimestamp(Date.now() + DAY * days + HOUR * hours + MINUTE * minutes + SECOND * seconds);
}, [days, hours, minutes, seconds]);

return (
<ResponsiveLayout fullWidth variant={themeVariant}>
<Box padding={16}>
<Stack space={16}>
<Text3 regular>
<TextTimer
dataAttributes={{testid: 'timer'}}
endTimestamp={endTimestamp}
labelType={labelType}
minTimeUnit={minTimeUnit === 'undefined' ? undefined : minTimeUnit}
maxTimeUnit={maxTimeUnit === 'undefined' ? undefined : maxTimeUnit}
onProgress={(currentValue) => {
if (!isEqual(currentValue, remainingTime)) {
setRemainingTime(currentValue);
}
}}
/>
</Text3>
<Stack space={8}>
<Title1 as="h2">onProgress callback value</Title1>
{remainingTime && (
<Text2 regular as="pre">
{JSON.stringify(remainingTime, null, 2)}
</Text2>
)}
</Stack>
</Stack>
</Box>
</ResponsiveLayout>
);
};

TextTimerStory.storyName = 'TextTimer';
TextTimerStory.args = {
labelType: 'none',
...baseArgs,
};
TextTimerStory.argTypes = {
...baseArgTypes,
labelType: {
options: ['none', 'short', 'long'],
control: {type: 'select'},
},
};

type TimerArgs = BaseArgs & {boxed: boolean};

export const TimerStory: StoryComponent<TimerArgs> = ({
themeVariant,
minTimeUnit,
maxTimeUnit,
days,
hours,
minutes,
seconds,
boxed,
}) => {
const [remainingTime, setRemainingTime] = React.useState<RemainingTime>();
const [endTimestamp, setEndTimestamp] = React.useState(
Date.now() + DAY * days + HOUR * hours + MINUTE * minutes + SECOND * seconds
);

React.useEffect(() => {
setEndTimestamp(Date.now() + DAY * days + HOUR * hours + MINUTE * minutes + SECOND * seconds);
}, [days, hours, minutes, seconds]);

return (
<ResponsiveLayout fullWidth variant={themeVariant}>
<Box padding={16}>
<Stack space={16}>
<Timer
dataAttributes={{testid: 'timer'}}
endTimestamp={endTimestamp}
minTimeUnit={minTimeUnit === 'undefined' ? undefined : minTimeUnit}
maxTimeUnit={maxTimeUnit === 'undefined' ? undefined : maxTimeUnit}
boxed={boxed}
onProgress={(currentValue) => {
if (!isEqual(currentValue, remainingTime)) {
setRemainingTime(currentValue);
}
}}
/>

<Stack space={8}>
<Title1 as="h2">onProgress callback value</Title1>
{remainingTime && (
<Text2 regular as="pre">
{JSON.stringify(remainingTime, null, 2)}
</Text2>
)}
</Stack>
</Stack>
</Box>
</ResponsiveLayout>
);
};

TimerStory.storyName = 'Timer';
TimerStory.args = {
...baseArgs,
boxed: false,
};
TimerStory.argTypes = {
...baseArgTypes,
};
Loading
Loading