Switch to scss, better scope rules (#170)

Restrict element types and global styling to improve embedability
This commit is contained in:
Will Scullin 2023-01-11 18:14:44 -08:00 committed by GitHub
parent 3d59142ee9
commit 9173bff1ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
66 changed files with 428 additions and 288 deletions

View File

@ -10,6 +10,12 @@
<link rel="apple-touch-icon" size="72x72" href="img/webapp-ipad.png" />
<link rel="shortcut icon" href="img/logoicon.png" />
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v6.1.1/css/all.css" />
<style>
body {
background-color: #c4c1a0;
margin: 16px 0;
}
</style>
</head>
<body>

View File

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

View File

@ -1,4 +1,5 @@
import 'preact/debug';
import cs from 'classnames';
import { h } from 'preact';
import { Header } from './Header';
import { Apple2 } from './Apple2';
@ -7,7 +8,8 @@ import { SYSTEM_TYPE_APPLE2E } from '../ui/system';
import { SCREEN_GL } from '../ui/screen';
import { defaultSystem, systemTypes } from './util/systems';
import styles from './css/App.module.css';
import styles from './css/App.module.scss';
import componentStyles from './css/Components.module.scss';
/**
* Top level application component, provides the parameters
@ -26,7 +28,7 @@ export const App = () => {
};
return (
<div className={styles.container}>
<div className={cs(styles.container, componentStyles.components)}>
<Header e={system.e} />
<Apple2
gl={gl}

View File

@ -16,7 +16,7 @@ import { ThunderClock } from './ThunderClock';
import { Videoterm } from './Videoterm';
import { spawn, Ready } from './util/promises';
import styles from './css/Apple2.module.css';
import styles from './css/Apple2.module.scss';
import { SupportedSectors } from 'js/formats/types';
declare global {

View File

@ -8,7 +8,8 @@ import { DiskDragTarget } from './DiskDragTarget';
import { DownloadModal } from './DownloadModal';
import { ErrorModal } from './ErrorModal';
import styles from './css/BlockDisk.module.css';
import styles from './css/BlockDisk.module.scss';
import { ControlButton } from './ControlButton';
/**
* Storage structure for drive state returned via callbacks.
@ -84,12 +85,8 @@ export const BlockDisk = ({ smartPort, number, on, name }: BlockDiskProps) => {
id={`disk${number}`}
className={cs(styles.diskLight, { [styles.on]: on })}
/>
<button title="Load Disk" onClick={onOpenModal}>
<i className="fas fa-folder-open" />
</button>
<button title="Save Disk" onClick={onOpenDownloadModal}>
<i className="fas fa-save" />
</button>
<ControlButton title="Load Disk" onClick={onOpenModal} icon="folder-open" />
<ControlButton title="Save Disk" onClick={onOpenDownloadModal} icon="save" />
<div
id={`disk-label${number}`}
className={styles.diskLabel}

View File

@ -9,7 +9,7 @@ import SmartPort from 'js/cards/smartport';
import { useHash } from './hooks/useHash';
import { noAwait } from './util/promises';
import styles from './css/BlockFileModal.module.css';
import styles from './css/BlockFileModal.module.scss';
const DISK_TYPES: FilePickerAcceptType[] = [
{

View File

@ -3,7 +3,7 @@ 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';
import styles from './css/CPUMeter.module.scss';
/**
* Interface for CPUMeter.

View File

@ -1,7 +1,7 @@
import { h, JSX } from 'preact';
import cs from 'classnames';
import styles from './css/ControlButton.module.css';
import styles from './css/ControlButton.module.scss';
/**
* Interface for ControlButton.
@ -23,7 +23,7 @@ export interface ControlButtonProps {
* @returns Control Button component
*/
export const ControlButton = ({ active, icon, title, onClick, ...props }: ControlButtonProps) => (
<button onClick={onClick} title={title} {...props} >
<button className={styles.iconButton} onClick={onClick} title={title} {...props} >
<i className={cs('fas', `fa-${icon}`, { [styles.active]: active })}></i>
</button>
);

View File

@ -13,7 +13,7 @@ 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';
import styles from './css/ControlStrip.module.scss';
import Apple2IO from 'js/apple2io';
const README = 'https://github.com/whscullin/apple2js#readme';

View File

@ -5,10 +5,11 @@ import Disk2 from '../cards/disk2';
import { ErrorModal } from './ErrorModal';
import { FileModal } from './FileModal';
import styles from './css/DiskII.module.css';
import styles from './css/DiskII.module.scss';
import { DiskDragTarget } from './DiskDragTarget';
import { FLOPPY_FORMATS } from 'js/formats/types';
import { DownloadModal } from './DownloadModal';
import { ControlButton } from './ControlButton';
/**
* Storage structure for Disk II state returned via callbacks.
@ -78,12 +79,8 @@ export const DiskII = ({ disk2, number, on, name, side }: DiskIIProps) => {
/>
<ErrorModal error={error} setError={setError} />
<div className={cs(styles.diskLight, { [styles.on]: on })} />
<button title="Load Disk" onClick={onOpenModal}>
<i className="fas fa-folder-open" />
</button>
<button title="Save Disk" onClick={onOpenDownloadModal}>
<i className="fas fa-save" />
</button>
<ControlButton title="Load Disk" onClick={onOpenModal} icon="folder-open" />
<ControlButton title="Save Disk" onClick={onOpenDownloadModal} icon="save" />
<div className={styles.diskLabel}>
{label}
</div>

View File

@ -3,7 +3,7 @@ import { useCallback, useEffect, useState } from 'preact/hooks';
import { DriveNumber, MassStorage } from '../formats/types';
import { Modal, ModalContent, ModalFooter } from './Modal';
import styles from './css/DownloadModal.module.css';
import styles from './css/DownloadModal.module.scss';
interface DownloadModalProps {
isOpen: boolean;

View File

@ -13,7 +13,7 @@ import { useHash } from './hooks/useHash';
import { DISK_FORMATS, DRIVE_NUMBERS, SupportedSectors } from 'js/formats/types';
import { spawn, Ready } from './util/promises';
import styles from './css/Drives.module.css';
import styles from './css/Drives.module.scss';
import { DiskDragTarget } from './DiskDragTarget';
/**

View File

@ -2,7 +2,7 @@ import { h } from 'preact';
import { useCallback } from 'preact/hooks';
import { Modal, ModalContent, ModalFooter } from './Modal';
import styles from './css/ErrorModal.module.css';
import styles from './css/ErrorModal.module.scss';
export interface ErrorProps {
error: unknown | undefined;

View File

@ -10,7 +10,7 @@ import { noAwait, spawn } from './util/promises';
import { useHash } from './hooks/useHash';
import { FileChooser, FilePickerAcceptType } from './FileChooser';
import styles from './css/FileModal.module.css';
import styles from './css/FileModal.module.scss';
const DISK_TYPES: FilePickerAcceptType[] = [
{

View File

@ -1,6 +1,6 @@
import { h } from 'preact';
import styles from './css/Header.module.css';
import styles from './css/Header.module.scss';
const README = 'https://github.com/whscullin/apple2js#readme';

View File

@ -1,7 +1,7 @@
import { h, ComponentChildren, JSX } from 'preact';
import cs from 'classnames';
import styles from './css/Inset.module.css';
import styles from './css/Inset.module.scss';
interface InsetProps extends JSX.HTMLAttributes<HTMLDivElement> {
children: ComponentChildren;

View File

@ -10,7 +10,7 @@ import {
mapKeyboardEvent
} from './util/keyboard';
import styles from './css/Keyboard.module.css';
import styles from './css/Keyboard.module.scss';
/**
* Convenience function for massaging key labels for upper

View File

@ -1,9 +1,12 @@
import { h, ComponentChildren } from 'preact';
import cs from 'classnames';
import { createPortal } from 'preact/compat';
import { useCallback } from 'preact/hooks';
import { useHotKey } from './hooks/useHotKey';
import styles from './css/Modal.module.css';
import styles from './css/Modal.module.scss';
import componentStyles from './css/Components.module.scss';
import { ControlButton } from './ControlButton';
/**
* ModalOverlay creates a semi-transparent overlay in which the
@ -39,9 +42,9 @@ export const ModalContent = ({ children }: { children: ComponentChildren }) => {
*/
export const ModalFooter = ({ children }: { children: ComponentChildren }) => {
return (
<footer className={styles.modalFooter}>
<div role="contentinfo" className={styles.modalFooter}>
{children}
</footer>
</div>
);
};
@ -64,9 +67,7 @@ export const ModalCloseButton = ({ onClose }: ModalCloseButtonProp) => {
useHotKey('Escape', doClose);
return (
<button onClick={doClose} title="Close">
{'\u2715'}
</button>
<ControlButton onClick={doClose} title="Close" icon="xmark" />
);
};
@ -91,14 +92,14 @@ export interface ModalHeaderProps {
*/
export const ModalHeader = ({ onClose, title, icon }: ModalHeaderProps) => {
return (
<header className={styles.modalHeader}>
<div role="banner" className={styles.modalHeader}>
<span className={styles.modalTitle}>
{icon && <i className={`fa-solid fa-${icon}`} role="img" />}
{' '}
{title}
</span>
{onClose && <ModalCloseButton onClose={onClose} />}
</header>
</div>
);
};
@ -132,7 +133,7 @@ export const Modal = ({
return (
isOpen ? createPortal((
<ModalOverlay>
<div className={styles.modal} role="dialog">
<div className={cs(styles.modal, componentStyles.components)} role="dialog">
{title && <ModalHeader title={title} {...props} />}
{children}
</div>

View File

@ -11,7 +11,7 @@ import {
SelectOption,
} from '../options';
import styles from './css/OptionsModal.module.css';
import styles from './css/OptionsModal.module.scss';
/**
* Boolean property interface

View File

@ -4,7 +4,7 @@ import Apple2IO, { slot } from 'js/apple2io';
import Parallel, { ParallelOptions } from 'js/cards/parallel';
import { Modal, ModalContent, ModalFooter } from './Modal';
import styles from './css/Printer.module.css';
import styles from './css/Printer.module.scss';
import { ControlButton } from './ControlButton';
import { byte } from 'js/types';

View File

@ -1,7 +1,7 @@
import { h } from 'preact';
import { Modal, ModalContent } from './Modal';
import styles from './css/ProgressModal.module.css';
import styles from './css/ProgressModal.module.scss';
export interface ErrorProps {
title: string;

View File

@ -1,6 +1,6 @@
import { h, Ref } from 'preact';
import styles from './css/Screen.module.css';
import styles from './css/Screen.module.scss';
/**
* Screen properties

View File

@ -2,7 +2,7 @@ import { ComponentChild, ComponentChildren, h } from 'preact';
import { useCallback, useState } from 'preact/hooks';
import cs from 'classnames';
import styles from './css/Tabs.module.css';
import styles from './css/Tabs.module.scss';
export interface TabProps {
children: ComponentChildren;

View File

@ -1,77 +0,0 @@
body {
margin: 16px 0;
font-size: 14px;
background-color: #c4c1a0; /* Pantone 453 */
font-family: sans-serif;
}
:global(.full-page) {
background: black;
}
a[role="button"] {
text-decoration: none;
}
button,
a[role="button"],
input[type="file"]::file-selector-button {
background: #44372c;
color: #fff;
padding: 2px 8px;
border: 1px outset #66594e;
border-radius: 3px;
font-size: 15px;
min-width: 75px;
}
button:hover,
a[role="button"]:hover,
input[type="file"]::file-selector-button {
background-color: #55473d;
border: 1px outset #66594e;
}
button:active,
a[role="button"]:active,
input[type="file"]::file-selector-button {
background-color: #22150a;
border: 1px outset #44372c;
}
button:focus,
a[role="button"]:focus,
input[type="file"]::file-selector-button {
outline: none;
}
.container {
display: flex;
flex-direction: column;
}
input[type="checkbox"] {
appearance: none;
background-color: #65594d;
border: 1px inset #65594d;
padding: 7px;
top: 7px;
border-radius: 3px;
display: inline-block;
position: relative;
}
input[type="checkbox"]:checked {
background-color: #65594d;
border: 1px inset #65594d;
color: #0d0;
}
input[type="checkbox"]:checked::after {
content: "\2716";
font-size: 12px;
position: absolute;
top: 0;
left: 2px;
color: #0d0;
}

View File

@ -0,0 +1,13 @@
:global(.full-page) {
background: black;
}
.container {
font-size: 14px;
font-family: sans-serif;
line-height: normal;
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
}

View File

@ -6,8 +6,8 @@
.outer {
width: 620px;
display: none;
}
.outer.ready {
&.ready {
display: block;
}
}

View File

@ -0,0 +1,71 @@
.components {
font-size: 14px;
font-family: sans-serif;
a[role="button"] {
text-decoration: none;
}
button,
a[role="button"],
input[type="file"]::file-selector-button {
background: #44372c;
color: #fff;
padding: 2px 8px;
border: 1px outset #66594e;
border-radius: 3px;
font-size: 15px;
min-width: 75px;
&.iconButton {
min-width: 36px;
margin: 0 2px;
}
}
button:hover,
a[role="button"]:hover,
input[type="file"]::file-selector-button {
background-color: #55473d;
border: 1px outset #66594e;
}
button:active,
a[role="button"]:active,
input[type="file"]::file-selector-button {
background-color: #22150a;
border: 1px outset #44372c;
}
button:focus,
a[role="button"]:focus,
input[type="file"]::file-selector-button {
outline: none;
}
input[type="checkbox"] {
appearance: none;
background-color: #65594d;
border: 1px inset #65594d;
padding: 7px;
top: 7px;
border-radius: 3px;
display: inline-block;
position: relative;
}
input[type="checkbox"]:checked {
background-color: #65594d;
border: 1px inset #65594d;
color: #0d0;
}
input[type="checkbox"]:checked::after {
content: "\2716";
font-size: 12px;
position: absolute;
top: 0;
left: 2px;
color: #0d0;
}
}

View File

@ -1,3 +0,0 @@
.active {
color: #0f0;
}

View File

@ -0,0 +1,8 @@
.active {
color: #0f0;
}
.iconButton {
min-width: 36px !important;
margin: 0 2px;
}

View File

@ -17,20 +17,21 @@
justify-content: center;
align-items: center;
user-select: none;
}
.reset:hover {
&:hover {
background: #44372c;
border: 3px outset #66594e;
}
.reset:active {
&:active {
background-color: #22150a;
border-left: 3px solid #44372c;
border-top: 3px solid #44372c;
border-right: 3px solid #000;
border-bottom: 3px solid #000;
}
}
:global(.full-page) .reset {
display: none;

View File

@ -10,8 +10,3 @@
:global(.full-page) .inset {
display: none;
}
.inset button {
min-width: 36px;
margin: 0 2px;
}

View File

@ -70,6 +70,26 @@
/* border: 5px outset #66594E; */
border-radius: 5px;
cursor: pointer;
.active {
color: #4f4;
}
.small {
font-size: 7px;
}
div {
border-radius: 3px;
position: absolute;
bottom: 0;
width: 100%;
text-align: center;
}
span {
font-size: 13px;
}
}
.pressed {
@ -82,14 +102,6 @@
/* border: 5px outset #44372C; */
}
.key div {
border-radius: 3px;
position: absolute;
bottom: 0;
width: 100%;
text-align: center;
}
.vCenter div {
bottom: 10px;
}
@ -99,22 +111,6 @@
line-height: 10px;
}
.key span {
font-size: 13px;
}
.key .small {
font-size: 7px;
}
.key-SHIFT {
width: 53px;
}
.active {
color: #4f4;
}
.key-RETURN {
width: 52px;
}
@ -134,11 +130,11 @@
background-color: #ffd;
color: black;
border-radius: 2px;
}
.key-POWER div {
div {
bottom: 15px;
}
}
.key-nbsp {
margin-left: 62px;

View File

@ -9,12 +9,12 @@
justify-content: center;
background: rgb(0 0 0 / 60%);
z-index: 1;
}
.modalOverlay button {
button {
min-width: 36px;
margin: 0 2px;
}
}
.modal {
background-color: #c4c1a0;
@ -36,11 +36,11 @@
border: 1px outset #66594e;
border-radius: 3px;
user-select: none;
}
.modalHeader button {
button {
min-width: 36px;
}
}
.modalTitle {
margin-top: 0;
@ -62,10 +62,11 @@
.modalFooter {
text-align: right;
user-select: none;
}
.modalFooter a[role="button"],
.modalFooter button {
a[role="button"],
button {
margin: 0 0 0 5px;
min-width: 75px;
}
}

View File

@ -6,14 +6,14 @@
font-weight: bold;
padding: 4px;
border-radius: 4px 4px 0 0;
}
.tab.selected {
.selected {
background-color: #c4c1a0;
border-bottom: none;
margin-bottom: -2px;
color: #080;
}
}
.tabs {
display: flex;

View File

@ -6,8 +6,8 @@ import ApplesoftDecompiler from 'js/applesoft/decompiler';
import { ApplesoftHeap, ApplesoftVariable } from 'js/applesoft/heap';
import { Apple2 as Apple2Impl } from 'js/apple2';
import styles from './css/Applesoft.module.css';
import debuggerStyles from './css/Debugger.module.css';
import styles from './css/Applesoft.module.scss';
import debuggerStyles from './css/Debugger.module.scss';
export interface ApplesoftProps {
apple2: Apple2Impl | undefined;

View File

@ -8,8 +8,8 @@ import { loadLocalBinaryFile } from '../util/files';
import { spawn } from '../util/promises';
import { toHex } from 'js/util';
import styles from './css/CPU.module.css';
import debuggerStyles from './css/Debugger.module.css';
import styles from './css/CPU.module.scss';
import debuggerStyles from './css/Debugger.module.scss';
export interface CPUProps {
apple2: Apple2Impl | undefined;
@ -29,7 +29,7 @@ const VALID_PAGE = /^[0-9A-F]{1,2}$/i;
const VALID_ADDRESS = /^[0-9A-F]{1,4}$/i;
const ERROR_ICON = (
<div className={styles.invalid}>
<div className={styles.errorIcon}>
<i
className="fa-solid fa-triangle-exclamation"
title="Invalid hex address"

View File

@ -10,7 +10,7 @@ import { Disks } from './Disks';
import { Memory } from './Memory';
import { VideoModes } from './VideoModes';
import styles from './css/Debugger.module.css';
import styles from './css/Debugger.module.scss';
interface DebuggerProps {
apple2: Apple2 | undefined;

View File

@ -15,8 +15,8 @@ import { FileEntry } from 'js/formats/prodos/file_entry';
import { VDH } from 'js/formats/prodos/vdh';
import { toHex } from 'js/util';
import styles from './css/Disks.module.css';
import debuggerStyles from './css/Debugger.module.css';
import styles from './css/Disks.module.scss';
import debuggerStyles from './css/Debugger.module.scss';
import { useCallback, useState } from 'preact/hooks';
import { DOS33, FileEntry as DOSEntry, isMaybeDOS33 } from 'js/formats/dos/dos33';
import createDiskFromDOS from 'js/formats/do';

View File

@ -3,7 +3,7 @@ import { h, Fragment } from 'preact';
import { useEffect, useRef, useState } from 'preact/hooks';
import { Modal, ModalContent, ModalFooter } from '../Modal';
import styles from './css/FileViewer.module.css';
import styles from './css/FileViewer.module.scss';
/**
* Binary and text representation of file to be previewed

View File

@ -6,8 +6,8 @@ import { Apple2 as Apple2Impl } from 'js/apple2';
import MMU from 'js/mmu';
import LanguageCard from 'js/cards/langcard';
import styles from './css/Memory.module.css';
import debuggerStyles from './css/Debugger.module.css';
import styles from './css/Memory.module.scss';
import debuggerStyles from './css/Debugger.module.scss';
/**
* Encapsulates the read/write status of a bank

View File

@ -5,8 +5,8 @@ import cs from 'classnames';
import { Apple2 as Apple2Impl } from 'js/apple2';
import { VideoPage } from 'js/videomodes';
import styles from './css/VideoModes.module.css';
import debuggerStyles from './css/Debugger.module.css';
import styles from './css/VideoModes.module.scss';
import debuggerStyles from './css/Debugger.module.scss';
export interface VideoModesProps {
apple2: Apple2Impl | undefined;

View File

@ -1,48 +0,0 @@
.listing {
width: calc(100% - 12px);
height: 320px;
overflow: auto;
white-space: pre-wrap;
}
.variables {
width: 100%;
height: 320px;
overflow: auto;
}
.variables table {
width: 100%;
}
.variables td {
background-color: #fff;
border: 1px inset;
white-space: pre;
font-family: monospace;
}
.internals {
width: 100%;
}
.internals table {
width: 100%;
}
.internals td {
background-color: #fff;
border: 1px inset;
white-space: pre;
font-family: monospace;
width: 30%;
}
.internals th {
width: 20%;
text-align: right;
}
.stack {
width: 10em;
}

View File

@ -0,0 +1,49 @@
.listing {
width: calc(100% - 12px);
height: 320px;
overflow: auto;
white-space: pre-wrap;
}
.variables {
width: 100%;
height: 320px;
overflow: auto;
table {
width: 100%;
}
td {
background-color: #fff;
border: 1px inset;
white-space: pre;
font-family: monospace;
}
}
.internals {
width: 100%;
table {
width: 100%;
}
td {
background-color: #fff;
border: 1px inset;
white-space: pre;
font-family: monospace;
width: 30%;
}
th {
width: 20%;
text-align: right;
}
}
.stack {
width: 10em;
}

View File

@ -23,13 +23,13 @@
color: #f00;
}
div.invalid {
.errorIcon {
position: relative;
display: inline-block;
}
div.invalid i {
i {
position: absolute;
top: -9px;
left: -16px;
}
}

View File

@ -130,19 +130,19 @@
position: relative;
}
div.read {
.read {
background-color: #8f8;
}
div.write {
.write {
background-color: #f88;
}
div.read.write {
.read.write {
background-color: #88f;
}
div.inactive {
.inactive {
background-color: #bbb;
}

117
package-lock.json generated
View File

@ -44,6 +44,8 @@
"node-forge": "^1.3.0",
"raw-loader": "^4.0.0",
"rimraf": "^3.0.2",
"sass": "^1.57.1",
"sass-loader": "^13.2.0",
"style-loader": "^3.3.1",
"stylelint": "^14.9.1",
"stylelint-config-css-modules": "^4.1.0",
@ -4960,9 +4962,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001342",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001342.tgz",
"integrity": "sha512-bn6sOCu7L7jcbBbyNhLg0qzXdJ/PMbybZTH/BA6Roet9wxYRm6Tr9D0s0uhLkOZ6MSG+QU6txUgdpr3MXIVqjA==",
"version": "1.0.30001442",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001442.tgz",
"integrity": "sha512-239m03Pqy0hwxYPYR5JwOIxRJfLTWtle9FV8zosfV5pHg+/51uD4nxcUlM8+mWWGfwKtt8lJNHnD3cWw9VZ6ow==",
"dev": true,
"funding": [
{
@ -7730,6 +7732,12 @@
"node": ">= 4"
}
},
"node_modules/immutable": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.1.tgz",
"integrity": "sha512-7WYV7Q5BTs0nlQm7tl92rDYYoyELLKHoDMBKhrxEoiV4mrfVdRz8hzPiYOzH7yWjzoVEamxRuAqhxL2PLRwZYQ==",
"dev": true
},
"node_modules/import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@ -11631,6 +11639,15 @@
"node": ">=6"
}
},
"node_modules/klona": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz",
"integrity": "sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==",
"dev": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/known-css-properties": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.25.0.tgz",
@ -13482,6 +13499,61 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true
},
"node_modules/sass": {
"version": "1.57.1",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.57.1.tgz",
"integrity": "sha512-O2+LwLS79op7GI0xZ8fqzF7X2m/m8WFfI02dHOdsK5R2ECeS5F62zrwg/relM1rjSLy7Vd/DiMNIvPrQGsA0jw==",
"dev": true,
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
"source-map-js": ">=0.6.2 <2.0.0"
},
"bin": {
"sass": "sass.js"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/sass-loader": {
"version": "13.2.0",
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.2.0.tgz",
"integrity": "sha512-JWEp48djQA4nbZxmgC02/Wh0eroSUutulROUusYJO9P9zltRbNN80JCBHqRGzjd4cmZCa/r88xgfkjGD0TXsHg==",
"dev": true,
"dependencies": {
"klona": "^2.0.4",
"neo-async": "^2.6.2"
},
"engines": {
"node": ">= 14.15.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependencies": {
"fibers": ">= 3.1.0",
"node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0",
"sass": "^1.3.0",
"sass-embedded": "*",
"webpack": "^5.0.0"
},
"peerDependenciesMeta": {
"fibers": {
"optional": true
},
"node-sass": {
"optional": true
},
"sass": {
"optional": true
},
"sass-embedded": {
"optional": true
}
}
},
"node_modules/saxes": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz",
@ -19663,9 +19735,9 @@
}
},
"caniuse-lite": {
"version": "1.0.30001342",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001342.tgz",
"integrity": "sha512-bn6sOCu7L7jcbBbyNhLg0qzXdJ/PMbybZTH/BA6Roet9wxYRm6Tr9D0s0uhLkOZ6MSG+QU6txUgdpr3MXIVqjA==",
"version": "1.0.30001442",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001442.tgz",
"integrity": "sha512-239m03Pqy0hwxYPYR5JwOIxRJfLTWtle9FV8zosfV5pHg+/51uD4nxcUlM8+mWWGfwKtt8lJNHnD3cWw9VZ6ow==",
"dev": true
},
"canvas": {
@ -21753,6 +21825,12 @@
"integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==",
"dev": true
},
"immutable": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.2.1.tgz",
"integrity": "sha512-7WYV7Q5BTs0nlQm7tl92rDYYoyELLKHoDMBKhrxEoiV4mrfVdRz8hzPiYOzH7yWjzoVEamxRuAqhxL2PLRwZYQ==",
"dev": true
},
"import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@ -24709,6 +24787,12 @@
"integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
"dev": true
},
"klona": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz",
"integrity": "sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==",
"dev": true
},
"known-css-properties": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.25.0.tgz",
@ -26079,6 +26163,27 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"dev": true
},
"sass": {
"version": "1.57.1",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.57.1.tgz",
"integrity": "sha512-O2+LwLS79op7GI0xZ8fqzF7X2m/m8WFfI02dHOdsK5R2ECeS5F62zrwg/relM1rjSLy7Vd/DiMNIvPrQGsA0jw==",
"dev": true,
"requires": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
"source-map-js": ">=0.6.2 <2.0.0"
}
},
"sass-loader": {
"version": "13.2.0",
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.2.0.tgz",
"integrity": "sha512-JWEp48djQA4nbZxmgC02/Wh0eroSUutulROUusYJO9P9zltRbNN80JCBHqRGzjd4cmZCa/r88xgfkjGD0TXsHg==",
"dev": true,
"requires": {
"klona": "^2.0.4",
"neo-async": "^2.6.2"
}
},
"saxes": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz",

View File

@ -53,6 +53,8 @@
"node-forge": "^1.3.0",
"raw-loader": "^4.0.0",
"rimraf": "^3.0.2",
"sass": "^1.57.1",
"sass-loader": "^13.2.0",
"style-loader": "^3.3.1",
"stylelint": "^14.9.1",
"stylelint-config-css-modules": "^4.1.0",

5
types/styles.d.ts vendored
View File

@ -2,3 +2,8 @@ declare module '*.module.css' {
const classes: { [key: string]: string };
export default classes;
}
declare module '*.module.scss' {
const classes: { [key: string]: string };
export default classes;
}

View File

@ -38,6 +38,24 @@ const baseConfig = {
'css-loader'
],
exclude: /\.module\.css$/
},
{
test: /\.module\.s(a|c)ss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
import: false,
modules: {
localIdentName: '[path][name]__[local]',
}
}
},
{
loader: 'sass-loader',
}
],
}
],
},