diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..13566b81 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/components/timeout-modal/.gitignore b/components/timeout-modal/.gitignore new file mode 100644 index 00000000..aa49ac15 --- /dev/null +++ b/components/timeout-modal/.gitignore @@ -0,0 +1,5 @@ +dist/ +node_modules/ +package-lock.json +pnpm-lock.yaml +tsconfig.tsbuildinfo diff --git a/components/timeout-modal/README.md b/components/timeout-modal/README.md new file mode 100644 index 00000000..913e98e5 --- /dev/null +++ b/components/timeout-modal/README.md @@ -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 => ( + +); + +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 +``` diff --git a/components/timeout-modal/assets/TimeoutModal.scss b/components/timeout-modal/assets/TimeoutModal.scss new file mode 100644 index 00000000..820b0179 --- /dev/null +++ b/components/timeout-modal/assets/TimeoutModal.scss @@ -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; + } + +} diff --git a/components/timeout-modal/jest.config.js b/components/timeout-modal/jest.config.js new file mode 100644 index 00000000..116a1cfb --- /dev/null +++ b/components/timeout-modal/jest.config.js @@ -0,0 +1,15 @@ +'use strict'; + +const baseConfig = require('../../jest.config.base'); + +const config = { + ...baseConfig, + collectCoverageFrom: [ + '/src/**.{ts,tsx}', + ], + testMatch: [ + '/spec/**.{ts,tsx}' + ] +}; + +module.exports = config; diff --git a/components/timeout-modal/package.json b/components/timeout-modal/package.json new file mode 100644 index 00000000..a108497a --- /dev/null +++ b/components/timeout-modal/package.json @@ -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 (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" + } +} diff --git a/components/timeout-modal/spec/TimeoutModal.stories.mdx b/components/timeout-modal/spec/TimeoutModal.stories.mdx new file mode 100644 index 00000000..968c370a --- /dev/null +++ b/components/timeout-modal/spec/TimeoutModal.stories.mdx @@ -0,0 +1,38 @@ +import { Meta, Preview, Props, Story } from '@storybook/addon-docs'; +import { TimeoutModal } from '../src/TimeoutModal'; +import readMe from '../README.md'; + + + +# timeout-modal + +HODS timeout modal + + + + + + + + + + +## Stories +### Standard + +A standard timeout-modal. + + + + + + diff --git a/components/timeout-modal/spec/TimeoutModal.ts b/components/timeout-modal/spec/TimeoutModal.ts new file mode 100644 index 00000000..d4f8fe27 --- /dev/null +++ b/components/timeout-modal/spec/TimeoutModal.ts @@ -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); + }); +}); diff --git a/components/timeout-modal/src/TimeoutModal.tsx b/components/timeout-modal/src/TimeoutModal.tsx new file mode 100644 index 00000000..bcf1547e --- /dev/null +++ b/components/timeout-modal/src/TimeoutModal.tsx @@ -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 = ({ + children, + classBlock, + classModifiers, + className, + isOpen, + timerDurationInSeconds = 300, + onContinue, + onSignout, + ...attrs +}) => { + const classes = classBuilder('hods-timeout-modal', classBlock, classModifiers, className); + + return ( + + + You will be signed out soon + To protect your information, you will be signed out in . + + Continue on this page + Sign out + + + + ); +}; + +TimeoutModal.displayName = 'TimeoutModal'; + +export default TimeoutModal; diff --git a/components/timeout-modal/src/Timer.tsx b/components/timeout-modal/src/Timer.tsx new file mode 100644 index 00000000..2f7c430b --- /dev/null +++ b/components/timeout-modal/src/Timer.tsx @@ -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 = ({ + 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 ( + {formattedTimer} {timer <= 60 ? "seconds" : formattedTimer == 1 ? "minute" : "minutes"} + ); +}; \ No newline at end of file diff --git a/components/timeout-modal/tsconfig.json b/components/timeout-modal/tsconfig.json new file mode 100644 index 00000000..a8aad5c9 --- /dev/null +++ b/components/timeout-modal/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.build.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": [ + "src" + ], + "exclude": [ + "dist", + "node_modules" + ] +}
To protect your information, you will be signed out in .