Use CSS modules (#123)

* Use CSS modules

* Fix tests

* Feedback
This commit is contained in:
Will Scullin 2022-06-03 15:30:39 -07:00 committed by GitHub
parent 203d89b8d9
commit 303838f63d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 1047 additions and 104 deletions

View File

@ -162,7 +162,7 @@ th {
display: flex;
}
.inset button, .modal-overlay button {
.inset button {
min-width: 36px;
margin: 0 2px;
}

View File

@ -5,8 +5,7 @@
<meta name="apple-mobile-web-app-capable" content="yes">
<link rel="shortcut icon" href="img/logoicon.png" />
<link rel="stylesheet" type="text/css" href="css/apple2.css" />
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.2/css/all.css" />
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v6.1.1/css/all.css" />
</head>
<body>
<div id="app"></div>

View File

@ -2,6 +2,7 @@ module.exports = {
'moduleNameMapper': {
'^js/(.*)': '<rootDir>/js/$1',
'^test/(.*)': '<rootDir>/test/$1',
'\\.css$': 'identity-obj-proxy',
},
'roots': [
'js/',

View File

@ -7,6 +7,8 @@ import { SYSTEM_TYPE_APPLE2E } from '../ui/system';
import { SCREEN_GL } from '../ui/screen';
import { defaultSystem, systemTypes } from './util/systems';
import './css/App.module.css';
/**
* Top level application component, provides the parameters
* needed by the Apple2 component to bootstrap itself.

View File

@ -14,6 +14,8 @@ import { Slinky } from './Slinky';
import { ThunderClock } from './ThunderClock';
import { ErrorModal } from './ErrorModal';
import styles from './css/Apple2.module.css';
/**
* Interface for the Apple2 component.
*/
@ -64,7 +66,7 @@ export const Apple2 = (props: Apple2Props) => {
}, [props]);
return (
<div className={cs('outer', { apple2e: e})}>
<div className={cs(styles.outer, { apple2e: e })}>
<Screen screen={screen} />
<Mouse cpu={cpu} screen={screen} io={io} slot={4} />
<Slinky io={io} slot={4} />

View File

@ -3,6 +3,8 @@ import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
import { Apple2 as Apple2Impl } from '../apple2';
import type { Stats } from '../apple2';
import styles from './css/CPUMeter.module.css';
/**
* Interface for CPUMeter.
*/
@ -63,7 +65,7 @@ export const CPUMeter = ({ apple2 }: CPUMeterProps) => {
}, []);
return (
<div id="khz" onClick={onClick}>
<div className={styles.khz} onClick={onClick}>
{mode === 0 && `${khz} Khz`}
{mode === 1 && `${fps} fps`}
{mode === 2 && `${rps} rps`}

View File

@ -13,6 +13,8 @@ import { JoyStick } from '../ui/joystick';
import { Screen, SCREEN_FULL_PAGE } from '../ui/screen';
import { System } from '../ui/system';
import styles from './css/ControlStrip.module.css';
const README = 'https://github.com/whscullin/apple2js#readme';
interface ControlStripProps {
@ -77,7 +79,7 @@ export const ControlStrip = ({ apple2, e }: ControlStripProps) => {
useHotKey('F12', doReset);
return (
<div id="reset-row">
<div className={styles.resetRow}>
<OptionsModal isOpen={showOptions} onClose={doCloseOptions} />
<Inset>
<CPUMeter apple2={apple2} />
@ -88,7 +90,7 @@ export const ControlStrip = ({ apple2, e }: ControlStripProps) => {
<ControlButton onClick={doShowOptions} title="Options (F4)" icon="cog" />
</Inset>
{e && (
<div id="reset" onClick={doReset}>
<div className={styles.reset} onClick={doReset}>
Reset
</div>
)}

View File

@ -7,6 +7,8 @@ import { loadJSON, loadHttpFile, getHashParts } from './util/files';
import { ErrorModal } from './ErrorModal';
import { useHash } from './hooks/useHash';
import styles from './css/DiskII.module.css';
/**
* Storage structure for Disk II state returned via callbacks.
*/
@ -73,20 +75,14 @@ export const DiskII = ({ disk2, number, on, name, side }: DiskIIProps) => {
}, []);
return (
<div className="disk">
<div className={styles.disk}>
<FileModal disk2={disk2} number={number} onClose={doClose} isOpen={modalOpen} />
<ErrorModal error={error} setError={setError} />
<div
id={`disk${number}`}
className={cs('disk-light', { on })}
/>
<div className={cs(styles.diskLight, { on })} />
<button title="Load Disk">
<i className="fas fa-folder-open" onClick={onOpenModal} />
</button>
<div
id={`disk-label${number}`}
className="disk-label"
>
<div className={styles.diskLabel}>
{label}
</div>
</div>

View File

@ -2,6 +2,8 @@ import { h } from 'preact';
import { useCallback } from 'preact/hooks';
import { Modal, ModalContent, ModalFooter } from './Modal';
import styles from './css/ErrorModal.module.css';
export interface ErrorProps {
error: unknown | undefined;
setError: (error: string | undefined) => void;
@ -30,7 +32,7 @@ export const ErrorModal = ({ error, setError }: ErrorProps) => {
onClose={onClose}
>
<ModalContent>
<div style={{ width: 320, fontSize: '1.1em', padding: '5px 11px' }}>
<div className={styles.errorModal}>
{errorStr}
</div>
</ModalContent>

View File

@ -11,6 +11,8 @@ import { noAwait } from './util/promises';
import { useHash } from './hooks/useHash';
import { FileChooser, FilePickerAcceptType } from './FileChooser';
import styles from './css/FileModal.module.css';
const DISK_TYPES: FilePickerAcceptType[] = [
{
description: 'Disk Images',
@ -106,7 +108,7 @@ export const FileModal = ({ disk2, number, onClose, isOpen }: FileModalProps) =>
<>
<Modal title="Open File" isOpen={isOpen}>
<ModalContent>
<div id="load-modal">
<div className={styles.loadModal}>
<select multiple onChange={doSelectCategory}>
{categoryNames.map((name) => (
<option key={name}>{name}</option>

View File

@ -1,5 +1,7 @@
import { h } from 'preact';
import styles from './css/Header.module.css';
const README = 'https://github.com/whscullin/apple2js#readme';
/**
@ -16,11 +18,13 @@ export interface HeaderProps {
*/
export const Header = ({ e }: HeaderProps) => {
return (
<div id="header">
<div className={styles.header}>
<a href={README} rel="noreferrer" target="_blank">
<img src="img/badge.png" id="badge" />
<img src="img/badge.png" className={styles.badge} />
</a>
<div id="subtitle">An Apple {e ? '//e' : ']['} Emulator in JavaScript</div>
<div className={styles.subtitle}>
An Apple {e ? '//e' : ']['} Emulator in JavaScript
</div>
</div>
);
};

View File

@ -1,12 +1,14 @@
import { h, ComponentChildren } from 'preact';
import styles from './css/Inset.module.css';
/**
* Convenience component for a nice beveled border.
*
* @returns Inset component
*/
export const Inset = ({ children }: { children: ComponentChildren }) => (
<div className="inset">
<div className={styles.inset}>
{children}
</div>
);

View File

@ -10,6 +10,8 @@ import {
keysAsTuples
} from './util/keyboard';
import styles from './css/Keyboard.module.css';
/**
* Convenience function for massaging key labels for upper
* and lower case
@ -21,7 +23,7 @@ const buildLabel = (key: string) => {
const small = key.length > 1 && !key.startsWith('&');
return (
<span
className={cs({ small })}
className={cs({[styles.small]: small})}
dangerouslySetInnerHTML={{ __html: key }}
/>
);
@ -60,19 +62,21 @@ export const Key = ({
}: KeyProps) => {
const keyName = lower.replace(/[&#;]/g, '');
const center =
lower === 'LOCK' ?
'v-center2' :
(upper === lower && upper.length > 1 ?
'v-center'
: ''
);
lower === 'LOCK'
? styles.vCenter2
: (upper === lower && upper.length > 1)
? styles.vCenter
: '';
return (
<div
className={cs(
'key',
`key-${keyName}`,
styles.key,
styles[`key-${keyName}`],
center,
{ pressed, active },
{
[styles.pressed]: pressed,
[styles.active]: active,
},
)}
data-key1={lower}
data-key2={upper}
@ -209,13 +213,13 @@ export const Keyboard = ({ apple2, e }: KeyboardProps) => {
/>;
const rows = keys.map((row, idx) =>
<div key={idx} className={`row row${idx}`}>
<div key={idx} className={cs(styles.row, styles[`row${idx}`])}>
{row.map(bindKey)}
</div>
);
return (
<div id="keyboard" style={{ marginLeft: e ? 0 : 15 }}>
<div className={styles.keyboard}>
{rows}
</div>
);

View File

@ -3,64 +3,7 @@ import { createPortal } from 'preact/compat';
import { useCallback } from 'preact/hooks';
import { useHotKey } from './hooks/useHotKey';
/**
* Temporary JS styling while I figure out how I really want
* to do it.
*/
const modalOverlayStyle = {
position: 'fixed',
left: '0',
right: '0',
top: '0',
bottom: '0',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: 'rgba(0,0,0,0.6)',
zIndex: 1,
};
const modalStyle = {
backgroundColor: '#c4c1a0',
padding: '10px',
maxHeight: '100vh',
borderRadius: '4px',
overflowY: 'auto',
boxSizing: 'border-box',
};
const modalHeaderStyle = {
display: 'flex',
fontSize: '14px',
justifyContent: 'space-between',
alignItems: 'center',
background: '#44372C',
color: '#fff',
padding: '5px 11px',
border: '1px outset #66594E',
borderRadius: '3px',
};
const modalTitleStyle = {
marginTop: 0,
marginBottom: 0,
fontWeight: 600,
fontSize: '1.25rem',
lineHeight: 1.25,
color: '#fff',
boxSizing: 'border-box',
};
const modalContentStyle = {
marginTop: '10px',
marginBottom: '10px',
lineHeight: 1.5,
color: '#000'
};
const modalFooterStyle = {
textAlign: 'right'
};
import styles from './css/Modal.module.css';
/**
* ModalOverlay creates a semi-transparent overlay in which the
@ -70,7 +13,7 @@ const modalFooterStyle = {
*/
export const ModalOverlay = ({ children }: { children: ComponentChildren }) => {
return (
<div style={modalOverlayStyle} className="modal-overlay">
<div className={styles.modalOverlay}>
{children}
</div>
);
@ -83,7 +26,7 @@ export const ModalOverlay = ({ children }: { children: ComponentChildren }) => {
*/
export const ModalContent = ({ children }: { children: ComponentChildren }) => {
return (
<div style={modalContentStyle}>
<div className={styles.modalContent}>
{children}
</div>
);
@ -96,7 +39,7 @@ export const ModalContent = ({ children }: { children: ComponentChildren }) => {
*/
export const ModalFooter = ({ children }: { children: ComponentChildren }) => {
return (
<footer style={modalFooterStyle}>
<footer className={styles.modalFooter}>
{children}
</footer>
);
@ -148,9 +91,9 @@ export interface ModalHeaderProps {
*/
export const ModalHeader = ({ onClose, title, icon }: ModalHeaderProps) => {
return (
<header style={modalHeaderStyle}>
<span style={modalTitleStyle}>
{icon && <i className={`fas fa-${icon}`} role="img" />}
<header className={styles.modalHeader}>
<span className={styles.modalTitle}>
{icon && <i className={`fa-solid fa-${icon}`} role="img" />}
{' '}
{title}
</span>
@ -189,7 +132,7 @@ export const Modal = ({
return (
isOpen ? createPortal((
<ModalOverlay>
<div style={modalStyle} role="dialog">
<div className={styles.modal} role="dialog">
{title && <ModalHeader title={title} {...props} />}
{children}
</div>

View File

@ -11,6 +11,8 @@ import {
SelectOption,
} from '../options';
import styles from './css/OptionsModal.module.css';
/**
* Boolean property interface
*/
@ -153,7 +155,7 @@ export const OptionsModal = ({ isOpen, onClose }: OptionsModalProps) => {
return (
<Modal title="Options" isOpen={isOpen} onClose={onClose}>
<ModalContent>
<div id="options-modal">
<div className={styles.optionsModal}>
{sections.map(makeSection)}
</div>
<i>* Reload page to take effect</i>

View File

@ -1,5 +1,7 @@
import { h, Ref } from 'preact';
import styles from './css/Screen.module.css';
/**
* Screen properties
*/
@ -16,9 +18,14 @@ export interface ScreenProps {
*/
export const Screen = ({ screen }: ScreenProps) => {
return (
<div id="display">
<div className="overscan">
<canvas id="screen" width="592" height="416" ref={screen} />
<div className={styles.display}>
<div className={styles.overscan}>
<canvas
className={styles.screen}
width="592"
height="416"
ref={screen}
/>
</div>
</div>
);

View File

@ -0,0 +1,34 @@
body {
margin: 16px 0;
font-size: 14px;
background-color: #c4c1a0; /* Pantone 453 */
font-family: sans-serif;
}
:global(.full-page) {
background: black;
}
button {
background: #44372C;
color: #fff;
padding: 2px 8px;
border: 1px outset #66594E;
border-radius: 3px;
font-size: 15px;
width: 75px;
}
button:hover {
background-color: #55473D;
border: 1px outset #66594E;
}
button:active {
background-color: #22150A;
border: 1px outset #44372C;
}
button:focus {
outline: none;
}

View File

@ -0,0 +1,4 @@
.outer {
margin: auto;
width: 620px;
}

View File

@ -0,0 +1,14 @@
.khz {
margin-right: 4px;
overflow: hidden;
padding: 4px;
font-family: Courier;
font-size: 12px;
width: 60px;
height: 15px;
text-align: right;
background: #000;
color: #0f0;
border: 2px inset #888;
border-radius: 4px;
}

View File

@ -0,0 +1,41 @@
.reset {
background: #44372C;
border-left: 3px solid #65594D;
border-top: 3px solid #65594D;
border-right: 3px solid #110E0D;
border-bottom: 3px solid #110E0D;
/* border: 5px outset #66594E; */
border-radius: 3px;
color: white;
font: 9px Helvetica;
height: 42px;
padding: 0;
margin-left: 10px;
width: 42px;
display: flex;
justify-content: center;
align-items: center;
}
.reset:hover {
background: #44372C;
border: 3px outset #66594E;
}
.reset:active {
background-color: #22150A;
border-left: 3px solid #44372C;
border-top: 3px solid #44372C;
border-right: 3px solid #000000;
border-bottom: 3px solid #000000;
}
:global(.full-page) .reset {
display: none;
}
.resetRow {
align-items: center;
display: flex;
margin: 10px 0;
}

View File

@ -0,0 +1,29 @@
.disk {
align-items: center;
display: flex;
flex-grow: 1;
max-width: 50%;
}
.diskLight {
margin: 5px;
background-image: url(../../../css/red-off-16.png);
flex-shrink: 0;
width: 16px;
height: 16px;
}
.diskLight.on {
background-image: url(../../../css/red-on-16.png);
}
.diskLabel {
color: #000;
font-family: sans-serif;
font-weight: bold;
margin-right: 0.5em;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
flex-grow: 1;
}

View File

@ -0,0 +1,5 @@
.errorModal {
width: 320px;
font-size: 1.1em;
padding: 5px 11px;
}

View File

@ -0,0 +1,5 @@
.loadModal select {
width: 250px;
height: 300px;
font-size: 14px;
}

View File

@ -0,0 +1,30 @@
.header {
width: 580px;
margin: auto;
}
.header img {
border: none;
}
:global(.full-page) .header {
display: none;
}
.badge {
cursor: pointer;
}
.subtitle {
margin: 0;
padding: 3px 0 0 10;
color: black;
font-family: "Adobe Garamond Pro",Garamond,Times;
font-size: 13px;
font-weight: normal;
}
.motter {
font-family: "Motter Tektura";
font-size: 12px;
}

View File

@ -0,0 +1,18 @@
.inset {
border-radius: 6px;
border: 3px inset #f0edd0;
padding: 6px;
margin: 10px auto;
display: flex;
width: 604px;
}
:global(.full-page) .inset {
display: none;
}
.inset button {
width: 36px;
margin: 0 2px;
}

View File

@ -0,0 +1,210 @@
.keyboard {
margin-left: 15px;
}
:global(.apple2e) .keyboard {
margin-left: 0;
}
.row {
display: block;
margin: 0;
padding: 0;
height: 42px;
font-family: Helvetica;
width: 570px;
}
:global(.apple2e) .row {
width: 610px;
}
.row0 {
margin-left: 20px;
}
.row2 {
margin-left: 10px;
}
.row3 {
margin-left: 10px;
}
.row4 {
margin-left: 10px;
}
:global(.apple2e) .row0 {
margin-left: 0;
}
:global(.apple2e) .row2 {
margin-left: 0;
}
:global(.apple2e) .row3 {
margin-left: 0;
}
:global(.apple2e) .row4 {
margin-left: 0;
}
.key {
display: inline-block;
margin: 0;
padding: 0;
width: 32px;
height: 32px;
background-color: #44372C;
color: white;
text-align: center;
position: relative;
border-left: 5px solid #65594D;
border-top: 5px solid #65594D;
border-right: 5px solid #110E0D;
border-bottom: 5px solid #110E0D;
/* border: 5px outset #66594E; */
border-radius: 5px;
cursor: pointer;
}
.pressed {
background-color: #22150A;
border-left: 5px solid #44372C;
border-top: 5px solid #44372C;
border-right: 5px solid #000000;
border-bottom: 5px solid #000000;
/* border: 5px outset #44372C; */
}
.key div {
border-radius: 3px;
position: absolute;
bottom: 0;
width: 100%;
text-align: center;
}
.vCenter div {
bottom: 10px;
}
.vCenter2 div {
bottom: 5px;
line-height: 10px;
}
.key span {
font-size: 13px;
}
.key .small {
font-size: 7px;
}
.key-SHIFT {
width: 53px;
}
.active {
color: #4f4;
}
.key-RETURN {
width: 52px;
}
.key-DELETE {
width: 47px;
}
.key-TAB {
width: 47px;
}
.key-POWER {
width: 38px;
height: 38px;
border: 2px groove black;
background-color: #ffd;
color: black;
border-radius: 2px;
}
.key-POWER div {
bottom: 15px;
}
.key-nbsp {
margin-left: 62px;
width: 330px;
}
:global(.apple2e) .key-CTRL {
width: 60px;
}
:global(.apple2e) .key-RETURN {
width: 60px;
}
:global(.apple2e) .key-SHIFT {
width: 81px;
}
:global(.apple2e) .key-nbsp {
margin-left: 0;
width: 215px;
}
.key-POW {
width: 38px;
height: 38px;
border: 2px groove black;
background-color: #ffd;
color: black;
border-radius: 2px;
}
.key-POW div {
bottom: 15px;
}
.key-POW span {
background: #4f4;
color: #4f4;
}
.key-OPEN_APPLE div {
background-image: url(../../../img/open-apple24.png);
width: 24px;
height: 24px;
bottom: 1px;
left: 4px;
}
.key-OPEN_APPLE span {
display: none;
}
.key-OPEN_APPLE.active div {
background-image: url(../../../img/open-apple24-green.png);
}
.key-CLOSED_APPLE div {
background-image: url(../../../img/closed-apple24.png);
width: 24px;
height: 24px;
bottom: 1px;
left: 4px;
}
.key-CLOSED_APPLE.active div {
background-image: url(../../../img/closed-apple24-green.png);
}
.key-CLOSED_APPLE span {
display: none;
}

View File

@ -0,0 +1,63 @@
.modalOverlay {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0,0,0,0.6);
z-index: 1;
}
.modalOverlay button {
min-width: 36px;
margin: 0 2px;
}
.modal {
background-color: #c4c1a0;
padding: 10px;
max-height: 100vh;
border-radius: 4px;
overflow-y: auto;
box-sizing: border-box;
}
.modalHeader {
display: flex;
font-size: 14px;
justify-content: space-between;
align-items: center;
background: #44372C;
color: #fff;
padding: 5px 11px;
border: 1px outset #66594E;
border-radius: 3px;
}
.modalHeader button {
width: 36px;
}
.modalTitle {
margin-top: 0;
margin-bottom: 0;
font-weight: 600;
font-size: 1.25rem;
line-height: 1.25;
color: #fff;
box-sizing: border-box;
}
.modalContent {
margin-top: 10px;
margin-bottom: 10px;
line-height: 1.5;
color: #000;
}
.modalFooter {
text-align: right;
}

View File

@ -0,0 +1,11 @@
.optionsModal {
width: 300px;
}
.optionsModal h3 {
font-size: 13px;
}
.optionsModal li {
list-style-type: none;
}

View File

@ -0,0 +1,105 @@
:global(.mono) {
filter: url('#green');
}
.display {
margin: 5px auto;
}
:global(.full-page) .display {
width: 100vw;
height: 68.5714vw; /* 384px / 560px * 100% */
max-height: 100vh;
max-width: 145.83vh; /* 560px / 384px * 100% */
padding: 0;
border: 0;
position: fixed;
top:0;
bottom: 0;
left: 0;
right: 0;
justify-content: center;
align-items: center;
margin: auto !important;
}
:global(.full-page) .overscan {
margin: initial;
padding: 0;
width: initial;
height: 384px;
border: 0;
border-radius: 0;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.overscan {
margin: auto;
position: relative;
background-color: black;
width: 592px;
height: 416px;
border: 6px inset #f0edd0;
border-radius: 10px;
}
:global(.full-page) .overscan {
margin: initial;
padding: 0;
width: initial;
height: 384px;
border: 0;
border-radius: 0;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
:global(.scanlines):after {
display: block;
pointer-events: none;
background-image: repeating-linear-gradient(to bottom, transparent 0, transparent 1px, rgba(0,0,0,0.5) 1px, rgba(0,0,0,0.5) 2px);
content: '';
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
:global(.full-page) :global(.scanlines):after {
background-image: repeating-linear-gradient(to bottom, transparent 0, transparent 0.25vh, rgba(0,0,0,0.5) 0.25vh, rgba(0,0,0,0.5) 0.5vh);
}
.screen {
cursor: crosshair;
-moz-image-rendering: -moz-crisp-edges;
-webkit-image-rendering: -webkit-optimize-contrast;
image-rendering: optimizeSpeed;
width: 592px;
height: 416px;
touch-action: manipulation;
user-select: none;
}
.screen.mouseMode {
cursor: none;
}
.screen:-webkit-full-screen {
background-color: black;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
}
:global(.full-page) .screen {
width: 100%;
height: 100%;
}

View File

@ -93,7 +93,7 @@ export class JoyStick implements OptionHandler {
return;
}
const s = document.querySelector<HTMLDivElement>('#screen')!;
const s = document.querySelector<HTMLDivElement>('canvas')!;
const offset = s.getBoundingClientRect();
let x = (evt.pageX - offset.left) / s.clientWidth;
let y = (evt.pageY - offset.top) / s.clientHeight;

374
package-lock.json generated
View File

@ -32,16 +32,19 @@
"ajv": "^6.12.0",
"babel-jest": "^27.2.4",
"canvas": "^2.8.0",
"css-loader": "^6.7.1",
"eslint": "^8.16.0",
"eslint-plugin-jest": "^26.4.3",
"eslint-plugin-react": "^7.30.0",
"eslint-plugin-react-hooks": "^4.5.0",
"file-loader": "^6.0.0",
"identity-obj-proxy": "^3.0.0",
"jest": "^27.2.4",
"jest-image-snapshot": "^4.5.1",
"node-forge": "^1.3.0",
"raw-loader": "^4.0.0",
"rimraf": "^3.0.2",
"style-loader": "^3.3.1",
"ts-jest": "^27.0.5",
"ts-loader": "^9.3.0",
"typescript": "^4.6.4",
@ -5230,6 +5233,47 @@
"source-map-resolve": "^0.6.0"
}
},
"node_modules/css-loader": {
"version": "6.7.1",
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz",
"integrity": "sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==",
"dev": true,
"dependencies": {
"icss-utils": "^5.1.0",
"postcss": "^8.4.7",
"postcss-modules-extract-imports": "^3.0.0",
"postcss-modules-local-by-default": "^4.0.0",
"postcss-modules-scope": "^3.0.0",
"postcss-modules-values": "^4.0.0",
"postcss-value-parser": "^4.2.0",
"semver": "^7.3.5"
},
"engines": {
"node": ">= 12.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependencies": {
"webpack": "^5.0.0"
}
},
"node_modules/css-loader/node_modules/semver": {
"version": "7.3.7",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
"integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/css.escape": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
@ -5245,6 +5289,18 @@
"node": ">=0.10.0"
}
},
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"dev": true,
"bin": {
"cssesc": "bin/cssesc"
},
"engines": {
"node": ">=4"
}
},
"node_modules/cssom": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz",
@ -7026,6 +7082,12 @@
"integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==",
"dev": true
},
"node_modules/harmony-reflect": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz",
"integrity": "sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==",
"dev": true
},
"node_modules/has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@ -7270,6 +7332,30 @@
"node": ">=0.10.0"
}
},
"node_modules/icss-utils": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz",
"integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==",
"dev": true,
"engines": {
"node": "^10 || ^12 || >= 14"
},
"peerDependencies": {
"postcss": "^8.1.0"
}
},
"node_modules/identity-obj-proxy": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz",
"integrity": "sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA==",
"dev": true,
"dependencies": {
"harmony-reflect": "^1.4.6"
},
"engines": {
"node": ">=4"
}
},
"node_modules/ignore": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
@ -11512,6 +11598,18 @@
"integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==",
"dev": true
},
"node_modules/nanoid": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
"dev": true,
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@ -12008,6 +12106,12 @@
"node": ">=8"
}
},
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
"dev": true
},
"node_modules/picomatch": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
@ -12097,6 +12201,108 @@
"ms": "^2.1.1"
}
},
"node_modules/postcss": {
"version": "8.4.14",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
"integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
}
],
"dependencies": {
"nanoid": "^3.3.4",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/postcss-modules-extract-imports": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz",
"integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==",
"dev": true,
"engines": {
"node": "^10 || ^12 || >= 14"
},
"peerDependencies": {
"postcss": "^8.1.0"
}
},
"node_modules/postcss-modules-local-by-default": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz",
"integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==",
"dev": true,
"dependencies": {
"icss-utils": "^5.0.0",
"postcss-selector-parser": "^6.0.2",
"postcss-value-parser": "^4.1.0"
},
"engines": {
"node": "^10 || ^12 || >= 14"
},
"peerDependencies": {
"postcss": "^8.1.0"
}
},
"node_modules/postcss-modules-scope": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz",
"integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==",
"dev": true,
"dependencies": {
"postcss-selector-parser": "^6.0.4"
},
"engines": {
"node": "^10 || ^12 || >= 14"
},
"peerDependencies": {
"postcss": "^8.1.0"
}
},
"node_modules/postcss-modules-values": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz",
"integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==",
"dev": true,
"dependencies": {
"icss-utils": "^5.0.0"
},
"engines": {
"node": "^10 || ^12 || >= 14"
},
"peerDependencies": {
"postcss": "^8.1.0"
}
},
"node_modules/postcss-selector-parser": {
"version": "6.0.10",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
"dev": true,
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
},
"engines": {
"node": ">=4"
}
},
"node_modules/postcss-value-parser": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"dev": true
},
"node_modules/preact": {
"version": "10.7.1",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.7.1.tgz",
@ -12932,6 +13138,15 @@
"node": ">=0.10.0"
}
},
"node_modules/source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/source-map-resolve": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz",
@ -13185,6 +13400,22 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/style-loader": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz",
"integrity": "sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==",
"dev": true,
"engines": {
"node": ">= 12.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependencies": {
"webpack": "^5.0.0"
}
},
"node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
@ -18655,12 +18886,45 @@
}
}
},
"css-loader": {
"version": "6.7.1",
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz",
"integrity": "sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==",
"dev": true,
"requires": {
"icss-utils": "^5.1.0",
"postcss": "^8.4.7",
"postcss-modules-extract-imports": "^3.0.0",
"postcss-modules-local-by-default": "^4.0.0",
"postcss-modules-scope": "^3.0.0",
"postcss-modules-values": "^4.0.0",
"postcss-value-parser": "^4.2.0",
"semver": "^7.3.5"
},
"dependencies": {
"semver": {
"version": "7.3.7",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
"integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
}
}
}
},
"css.escape": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
"integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==",
"dev": true
},
"cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"dev": true
},
"cssom": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz",
@ -20006,6 +20270,12 @@
"integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==",
"dev": true
},
"harmony-reflect": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz",
"integrity": "sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==",
"dev": true
},
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@ -20200,6 +20470,22 @@
"safer-buffer": ">= 2.1.2 < 3"
}
},
"icss-utils": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz",
"integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==",
"dev": true,
"requires": {}
},
"identity-obj-proxy": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz",
"integrity": "sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA==",
"dev": true,
"requires": {
"harmony-reflect": "^1.4.6"
}
},
"ignore": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
@ -23413,6 +23699,12 @@
"integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==",
"dev": true
},
"nanoid": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
"dev": true
},
"natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@ -23777,6 +24069,12 @@
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
"dev": true
},
"picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
"dev": true
},
"picomatch": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
@ -23846,6 +24144,69 @@
}
}
},
"postcss": {
"version": "8.4.14",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
"integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==",
"dev": true,
"requires": {
"nanoid": "^3.3.4",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
}
},
"postcss-modules-extract-imports": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz",
"integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==",
"dev": true,
"requires": {}
},
"postcss-modules-local-by-default": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz",
"integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==",
"dev": true,
"requires": {
"icss-utils": "^5.0.0",
"postcss-selector-parser": "^6.0.2",
"postcss-value-parser": "^4.1.0"
}
},
"postcss-modules-scope": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz",
"integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==",
"dev": true,
"requires": {
"postcss-selector-parser": "^6.0.4"
}
},
"postcss-modules-values": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz",
"integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==",
"dev": true,
"requires": {
"icss-utils": "^5.0.0"
}
},
"postcss-selector-parser": {
"version": "6.0.10",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
"dev": true,
"requires": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
}
},
"postcss-value-parser": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"dev": true
},
"preact": {
"version": "10.7.1",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.7.1.tgz",
@ -24498,6 +24859,12 @@
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
"dev": true
},
"source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"dev": true
},
"source-map-resolve": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz",
@ -24705,6 +25072,13 @@
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
"dev": true
},
"style-loader": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz",
"integrity": "sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==",
"dev": true,
"requires": {}
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",

View File

@ -41,16 +41,19 @@
"ajv": "^6.12.0",
"babel-jest": "^27.2.4",
"canvas": "^2.8.0",
"css-loader": "^6.7.1",
"eslint": "^8.16.0",
"eslint-plugin-jest": "^26.4.3",
"eslint-plugin-react": "^7.30.0",
"eslint-plugin-react-hooks": "^4.5.0",
"file-loader": "^6.0.0",
"identity-obj-proxy": "^3.0.0",
"jest": "^27.2.4",
"jest-image-snapshot": "^4.5.1",
"node-forge": "^1.3.0",
"raw-loader": "^4.0.0",
"rimraf": "^3.0.2",
"style-loader": "^3.3.1",
"ts-jest": "^27.0.5",
"ts-loader": "^9.3.0",
"typescript": "^4.6.4",

View File

@ -39,6 +39,7 @@
"include": [
"js/**/*",
"test/**/*",
"types/**/*",
"*.config.js"
]
}

4
types/styles.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
declare module '*.module.css' {
const classes: { [key: string]: string };
export default classes;
}

View File

@ -15,6 +15,28 @@ const baseConfig = {
],
exclude: /node_modules/,
},
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
import: false,
modules: true
}
}
],
include: /\.module\.css$/
},
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
],
exclude: /\.module\.css$/
}
],
},
output: {