Skip to content

Commit

Permalink
add timout time modal code
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelthorpe committed Sep 14, 2023
1 parent 442987d commit 33adaf2
Show file tree
Hide file tree
Showing 11 changed files with 381 additions and 0 deletions.
8 changes: 8 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions components/timeout-modal/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
dist/
node_modules/
package-lock.json
pnpm-lock.yaml
tsconfig.tsbuildinfo
67 changes: 67 additions & 0 deletions components/timeout-modal/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
Hods - Timeout-Modal
====================

HODS timeout modal


Using this package
------------------

First install the package into your project:

```shell
npm install -S @hods/timeout-modal
```

Then use it in your code as follows:

```js
import React, { createElement as h } from 'react';
import TimeoutModal from '@hods/timeout-modal';

export const MyComponent = props => (
<TimeoutModal
// WRITEME
/>
);

export default MyComponent;
```


Working on this package
-----------------------

Before working on this package you must install its dependencies using
the following command:

```shell
pnpm install
```


### Testing

Run the unit tests.

```shell
npm test
```


### Building

Build the package by compiling the TypeScript source code.

```shell
npm run build
```


### Clean-up

Remove any previously built files.

```shell
npm run clean
```
58 changes: 58 additions & 0 deletions components/timeout-modal/assets/TimeoutModal.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
@import "@hods/sass-base";
@import "govuk-frontend/govuk/objects/_button-group";
@import "govuk-frontend/govuk/components/button/_button";
@import "govuk-frontend/govuk/core/_typography";
@import "govuk-frontend/govuk/core/_links";

.hods-timeout-modal {
&__overlay {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background-color: rgba(0,0,0,0.8);
z-index: 666;
}
&__content {
min-width: 200px;
max-width: 340px;
@include govuk-responsive-padding(6);
background-color: #fff;
border: 3px solid #000000;

h1 {
@extend .govuk-heading-l;
}

p {
@extend .govuk-body;
}
}
&__buttons {
@extend .govuk-button-group;
@include govuk-responsive-margin(0, 'bottom');

button {
@extend .govuk-button;

@include govuk-media-query($from: tablet) {
@include govuk-responsive-margin(0, 'bottom');
}
}

a {
@extend .govuk-link;
@include govuk-responsive-margin(0);

@include govuk-media-query($from: tablet) {
@include govuk-responsive-margin(3, 'left');
}
}
}
&__timer {
font-weight: $govuk-font-weight-bold;
white-space: nowrap;
}

}
15 changes: 15 additions & 0 deletions components/timeout-modal/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use strict';

const baseConfig = require('../../jest.config.base');

const config = {
...baseConfig,
collectCoverageFrom: [
'<rootDir>/src/**.{ts,tsx}',
],
testMatch: [
'<rootDir>/spec/**.{ts,tsx}'
]
};

module.exports = config;
53 changes: 53 additions & 0 deletions components/timeout-modal/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"name": "@hods/timeout-modal",
"version": "0.4.0",
"description": "HODS timeout modal",
"main": "src/TimeoutModal.tsx",
"sass": "assets/TimeoutModal.scss",
"publishConfig": {
"main": "dist/TimeoutModal.js",
"typings": "dist/TimeoutModal.d.ts"
},
"files": [
"/assets",
"/dist"
],
"scripts": {
"test": "NODE_OPTIONS=--experimental-vm-modules jest",
"prepublishOnly": "npm run clean && npm run build",
"build": "tsc",
"clean": "rm -rf dist tsconfig.tsbuildinfo"
},
"author": "Daniel A.C. Martin <[email protected]> (http://daniel-martin.co.uk/)",
"license": "MIT",
"keywords": [
"react-components"
],
"dependencies": {
"@hods/sass-base": "workspace:^0.4.0",
"@not-govuk/component-helpers": "^0.7.2",
"govuk-frontend": "4.4.1"
},
"peerDependencies": {
"@not-govuk/docs-components": "^0.7.2",
"@storybook/addon-docs": "^6.4.0",
"react": "^16.9.55"
},
"peerDependenciesMeta": {
"@not-govuk/docs-components": {
"optional": true
},
"@storybook/addon-docs": {
"optional": true
}
},
"devDependencies": {
"@mdx-js/react": "1.6.22",
"@not-govuk/component-test-helpers": "^0.7.2",
"@types/react": "16.14.32",
"jest": "29.2.2",
"jest-environment-jsdom": "29.2.2",
"ts-jest": "29.0.3",
"typescript": "4.8.4"
}
}
38 changes: 38 additions & 0 deletions components/timeout-modal/spec/TimeoutModal.stories.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Meta, Preview, Props, Story } from '@storybook/addon-docs';
import { TimeoutModal } from '../src/TimeoutModal';
import readMe from '../README.md';

<Meta
title="Timeout Modal"
component={ TimeoutModal }
parameters={ {
chromatic: { viewports: [640, 480] },
description: 'HODS timeout modal',
jest: ['TimeoutModal'],
notes: readMe
} }
/>

# timeout-modal

HODS timeout modal

<Preview withToolbar>
<Story name="TimeoutModal">
<TimeoutModal />
</Story>
</Preview>

<Props of={ TimeoutModal } />


## Stories
### Standard

A standard timeout-modal.

<Preview>
<Story name="Standard">
<TimeoutModal />
</Story>
</Preview>
23 changes: 23 additions & 0 deletions components/timeout-modal/spec/TimeoutModal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { createElement as h } from 'react';
import { mount } from '@not-govuk/component-test-helpers';
import TimeoutModal from '../src/TimeoutModal';

describe('TimeoutModal', () => {
const minimalProps = {
};

describe('when given minimal valid props', () => {
const component = mount(h(TimeoutModal, minimalProps, 'Child'));

it('renders', () => undefined);
});

describe('when given all valid props', () => {
const props = {
...minimalProps
};
const component = mount(h(TimeoutModal, props, 'Child'));

it('renders', () => undefined);
});
});
47 changes: 47 additions & 0 deletions components/timeout-modal/src/TimeoutModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { FC, createElement as h, useState } from 'react';
import { StandardProps, classBuilder } from '@not-govuk/component-helpers';

import '../assets/TimeoutModal.scss';
import { Timer } from './Timer';

export type TimeoutModalProps = StandardProps & {
/** Determines whether the modal should be opend or closed */
isOpen: boolean,
/** Starting value of the timeout timer in seconds */
timerDurationInSeconds: number,
/** Function called when the user clicks the modal button */
onContinue: () => void,
/** Function called when the user clicks the modal link */
onSignout: () => void,
};

export const TimeoutModal: FC<TimeoutModalProps> = ({
children,
classBlock,
classModifiers,
className,
isOpen,
timerDurationInSeconds = 300,
onContinue,
onSignout,
...attrs
}) => {
const classes = classBuilder('hods-timeout-modal', classBlock, classModifiers, className);

return (
<div {...attrs} className={classes('overlay')}>
<div role='dialog' aria-labelledby='modalTitle' aria-describedby='modalContent' className={classes('content')}>
<h1 id='modalTitle'>You will be signed out soon</h1>
<p id='modalContent' aria-live='polite'>To protect your information, you will be signed out in <Timer className={classes('timer')} timerFrom={timerDurationInSeconds}/>.</p>
<div className={classes('buttons')}>
<button role='button' onClick={onContinue}>Continue on this page</button>
<a role='link' onClick={onSignout}>Sign out</a>
</div>
</div>
</div>
);
};

TimeoutModal.displayName = 'TimeoutModal';

export default TimeoutModal;
53 changes: 53 additions & 0 deletions components/timeout-modal/src/Timer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { FC, createElement as h, useState, useEffect } from 'react';
import { StandardProps } from '@not-govuk/component-helpers';

export type TimerProps = StandardProps & {
/** Timeout starting value in seconds */
timerFrom: number
};

export const Timer: FC<TimerProps> = ({
timerFrom,
...attrs
}) => {
const [runTimer, setRunTimer] = useState(true)
const [timer, setTimer] = useState(timerFrom);
const [formattedTimer, setFormattedTimer] = useState(Math.floor(timer / 60));

useEffect(() => {
let interval;
if(runTimer) {
interval = setInterval(() => {
setTimer((timer) => timer - 1);
}, 1000);
} else {
clearInterval(interval);
}
return () => clearInterval(interval);
}, [runTimer])

useEffect(() => {
if(timer > 60) {
if(timer % 60 == 0) {
setFormattedTimer(Math.round(timer / 60));
}
} else {
if([60,40,20,0].includes(timer)) {
setFormattedTimer(timer);
if(timer == 0) {
setRunTimer(false);
}
}
}
}, [timer])

useEffect(() => {
return () => {
setRunTimer(false);
};
}, []);

return (
<span {...attrs}>{formattedTimer} {timer <= 60 ? "seconds" : formattedTimer == 1 ? "minute" : "minutes"}</span>
);
};
14 changes: 14 additions & 0 deletions components/timeout-modal/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"extends": "../../tsconfig.build.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": [
"src"
],
"exclude": [
"dist",
"node_modules"
]
}

1 comment on commit 33adaf2

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.