diff --git a/.eslintrc.js b/.eslintrc.js
index cbb76f8..37687f4 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -3,6 +3,7 @@ module.exports = {
"plugin:react/recommended", // Uses the recommended rules from @eslint-plugin-react
"plugin:@typescript-eslint/recommended", // Uses the recommended rules from the @typescript-eslint/eslint-plugin
"plugin:prettier/recommended", // Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors.
+ "plugin:css-modules/recommended",
],
parser: "@typescript-eslint/parser",
parserOptions: {
@@ -12,7 +13,7 @@ module.exports = {
jsx: true, // Allows for the parsing of JSX
},
},
- plugins: ["@typescript-eslint"],
+ plugins: ["@typescript-eslint", "css-modules"],
settings: {
"import/resolver": {
node: {
diff --git a/declarations.d.ts b/declarations.d.ts
new file mode 100644
index 0000000..8f8fc24
--- /dev/null
+++ b/declarations.d.ts
@@ -0,0 +1,24 @@
+declare module "*.module.css" {
+ const classes: { [key: string]: string };
+ export default classes;
+}
+
+declare module "*.module.scss" {
+ const classes: { [key: string]: string };
+ export default classes;
+}
+
+declare module "*.module.sass" {
+ const classes: { [key: string]: string };
+ export default classes;
+}
+
+declare module "*.module.less" {
+ const classes: { [key: string]: string };
+ export default classes;
+}
+
+declare module "*.module.styl" {
+ const classes: { [key: string]: string };
+ export default classes;
+}
diff --git a/package.json b/package.json
index d4c6eb6..309d5bf 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,9 @@
"dependencies": {
"@babel/runtime": "^7.13.17",
"@bluelovers/fast-glob": "https://github.com/rishighan/fast-glob-v2-api.git",
+ "@dnd-kit/core": "^4.0.0",
+ "@dnd-kit/modifiers": "^4.0.0",
+ "@dnd-kit/sortable": "^5.0.0",
"@types/event-stream": "^3.3.34",
"@types/mime-types": "^2.1.0",
"@types/react": "^17.0.3",
@@ -112,10 +115,12 @@
"concurrently": "^4.0.0",
"connected-react-router": "^6.9.1",
"css-loader": "^5.1.2",
+ "css-modules-typescript-loader": "^4.0.1",
"eslint": "^7.22.0",
"eslint-config-airbnb": "^18.2.1",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-config-prettier": "^8.1.0",
+ "eslint-plugin-css-modules": "^2.11.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jsx-a11y": "^6.0.3",
"eslint-plugin-prettier": "^3.3.1",
diff --git a/src/client/assets/scss/App.scss b/src/client/assets/scss/App.scss
index 51b7c7a..b6acc06 100644
--- a/src/client/assets/scss/App.scss
+++ b/src/client/assets/scss/App.scss
@@ -294,24 +294,36 @@ $border-color: red;
}
}
}
+
+ .carousel-status {
+ position: absolute;
+ background-color: #999;
+ text-shadow: none;
+ border-radius: 5px;
+ top: 10px;
+ right: 30px;
+ padding: 5px;
+ font-size: 10px;
+ color: #fff;
+ }
+ // next and prev controls
.control-next {
border-top-right-radius: 10px;
border-bottom-right-radius: 10px;
- &:hover {
- border-top-right-radius: 10px;
- border-bottom-right-radius: 10px;
+ &:hover {
+ border-top-right-radius: 10px;
+ border-bottom-right-radius: 10px;
+ }
}
-}
.control-prev {
- border-top-left-radius: 10px;
- border-bottom-left-radius: 10px;
- &:hover {
border-top-left-radius: 10px;
border-bottom-left-radius: 10px;
+ &:hover {
+ border-top-left-radius: 10px;
+ border-bottom-left-radius: 10px;
+ }
}
}
-
-}
// airdcpp downloads tab
.tabs {
diff --git a/src/client/components/ComicDetail.tsx b/src/client/components/ComicDetail.tsx
index d842b57..7e96cad 100644
--- a/src/client/components/ComicDetail.tsx
+++ b/src/client/components/ComicDetail.tsx
@@ -7,21 +7,16 @@ import ComicVineSearchForm from "./ComicVineSearchForm";
import AcquisitionPanel from "./AcquisitionPanel";
import DownloadsPanel from "./DownloadsPanel";
import SlidingPane from "react-sliding-pane";
-import "react-responsive-carousel/lib/styles/carousel.min.css";
-import { Carousel } from "react-responsive-carousel";
-
import Select, { components } from "react-select";
+import { RemovableItems } from "./SortableGrid/SortableGrid";
import "react-sliding-pane/dist/react-sliding-pane.css";
import "react-loader-spinner/dist/loader/css/react-spinner-loader.css";
import Loader from "react-loader-spinner";
-import { isEmpty, isUndefined, isNil, map } from "lodash";
+import { isEmpty, isUndefined, isNil } from "lodash";
import { RootState } from "threetwo-ui-typings";
import { fetchComicVineMatches } from "../actions/fileops.actions";
-import {
- extractComicArchive,
- getComicBookDetailById,
-} from "../actions/comicinfo.actions";
+import { getComicBookDetailById } from "../actions/comicinfo.actions";
import { detectTradePaperbacks } from "../shared/utils/tradepaperback.utils";
import dayjs from "dayjs";
const prettyBytes = require("pretty-bytes");
@@ -136,43 +131,7 @@ export const ComicDetail = ({}: ComicDetailProps): ReactElement => {
},
},
editComicArchive: {
- content: () => (
- <>
-
-
- Pages
-
- {extractedComicBookArchive.length}
-
-
-
-
- {map(extractedComicBookArchive, (page, idx) => {
- return (
-
-

-
- );
- })}
-
- >
- ),
+ content: () => <>>,
},
};
@@ -182,25 +141,6 @@ export const ComicDetail = ({}: ComicDetailProps): ReactElement => {
setVisible(true);
}, [dispatch, comicBookDetailData]);
- const openDrawerForEditingComicArchive = useCallback(() => {
- dispatch(
- extractComicArchive(
- comicBookDetailData.rawFileDetails.containedIn +
- "/" +
- comicBookDetailData.rawFileDetails.name +
- comicBookDetailData.rawFileDetails.extension,
- {
- extractTarget: "book",
- targetExtractionFolder:
- "./userdata/expanded/" + comicBookDetailData.rawFileDetails.name,
- extractionMode: "all",
- },
- ),
- );
- setSlidingPanelContentId("editComicArchive");
- setVisible(true);
- }, [dispatch, comicBookDetailData, extractedComicBookArchive]);
-
const [active, setActive] = useState(1);
const createDescriptionMarkup = (html) => {
return { __html: html };
@@ -274,9 +214,13 @@ export const ComicDetail = ({}: ComicDetailProps): ReactElement => {
},
{
id: 2,
- icon: ,
- name: "Other Metadata",
- content: bastard
,
+ icon: ,
+ name: "Archive Operations",
+ content: (
+
+
+
+ ),
},
{
id: 3,
@@ -449,7 +393,7 @@ export const ComicDetail = ({}: ComicDetailProps): ReactElement => {
};
// Determine which cover image to use:
- // 1. from the locally imported, non-CV-scraped version, or
+ // 1. from the locally imported or
// 2. from the CV-scraped version
let imagePath = "";
let comicBookTitle = "";
@@ -506,9 +450,6 @@ export const ComicDetail = ({}: ComicDetailProps): ReactElement => {
case "match-on-comic-vine":
openDrawerWithCVMatches();
break;
- case "edit-comic-archive":
- openDrawerForEditingComicArchive();
- break;
default:
console.log("No valid action selected.");
break;
diff --git a/src/client/components/SortableGrid/Sortable.tsx b/src/client/components/SortableGrid/Sortable.tsx
new file mode 100644
index 0000000..1a942c4
--- /dev/null
+++ b/src/client/components/SortableGrid/Sortable.tsx
@@ -0,0 +1,329 @@
+import React, { useEffect, useRef, useState } from "react";
+import { createPortal } from "react-dom";
+
+import {
+ Announcements,
+ closestCenter,
+ CollisionDetection,
+ DragOverlay,
+ DndContext,
+ DropAnimation,
+ defaultDropAnimation,
+ KeyboardSensor,
+ Modifiers,
+ MouseSensor,
+ MeasuringConfiguration,
+ PointerActivationConstraint,
+ ScreenReaderInstructions,
+ TouchSensor,
+ UniqueIdentifier,
+ useSensor,
+ useSensors,
+} from "@dnd-kit/core";
+import {
+ arrayMove,
+ useSortable,
+ SortableContext,
+ sortableKeyboardCoordinates,
+ SortingStrategy,
+ rectSortingStrategy,
+ AnimateLayoutChanges,
+} from "@dnd-kit/sortable";
+
+import { createRange } from "./utilities";
+import { Item, List, Wrapper } from "./components";
+
+export interface Props {
+ activationConstraint?: PointerActivationConstraint;
+ animateLayoutChanges?: AnimateLayoutChanges;
+ adjustScale?: boolean;
+ collisionDetection?: CollisionDetection;
+ Container?: any; // To-do: Fix me
+ dropAnimation?: DropAnimation | null;
+ itemCount?: number;
+ items?: string[];
+ handle?: boolean;
+ measuring?: MeasuringConfiguration;
+ modifiers?: Modifiers;
+ renderItem?: any;
+ removable?: boolean;
+ strategy?: SortingStrategy;
+ useDragOverlay?: boolean;
+ getItemStyles?(args: {
+ id: UniqueIdentifier;
+ index: number;
+ isSorting: boolean;
+ isDragOverlay: boolean;
+ overIndex: number;
+ isDragging: boolean;
+ }): React.CSSProperties;
+ wrapperStyle?(args: {
+ index: number;
+ isDragging: boolean;
+ id: string;
+ }): React.CSSProperties;
+ isDisabled?(id: UniqueIdentifier): boolean;
+}
+
+const defaultDropAnimationConfig: DropAnimation = {
+ ...defaultDropAnimation,
+ dragSourceOpacity: 0.5,
+};
+
+const screenReaderInstructions: ScreenReaderInstructions = {
+ draggable: `
+ To pick up a sortable item, press the space bar.
+ While sorting, use the arrow keys to move the item.
+ Press space again to drop the item in its new position, or press escape to cancel.
+ `,
+};
+
+export function Sortable({
+ activationConstraint,
+ animateLayoutChanges,
+ adjustScale = false,
+ Container = List,
+ collisionDetection = closestCenter,
+ dropAnimation = defaultDropAnimationConfig,
+ getItemStyles = () => ({}),
+ handle = false,
+ itemCount = 16,
+ items: initialItems,
+ isDisabled = () => false,
+ measuring,
+ modifiers,
+ removable,
+ renderItem,
+ strategy = rectSortingStrategy,
+ useDragOverlay = true,
+ wrapperStyle = () => ({}),
+}: Props) {
+ const [items, setItems] = useState(
+ () =>
+ initialItems ??
+ createRange(itemCount, (index) => (index + 1).toString()),
+ );
+ const [activeId, setActiveId] = useState(null);
+ const sensors = useSensors(
+ useSensor(MouseSensor, {
+ activationConstraint,
+ }),
+ useSensor(TouchSensor, {
+ activationConstraint,
+ }),
+ useSensor(KeyboardSensor, {
+ coordinateGetter: sortableKeyboardCoordinates,
+ }),
+ );
+ const isFirstAnnouncement = useRef(true);
+ const getIndex = items.indexOf.bind(items);
+ const getPosition = (id: string) => getIndex(id) + 1;
+ const activeIndex = activeId ? getIndex(activeId) : -1;
+ const handleRemove = removable
+ ? (id: string) => setItems((items) => items.filter((item) => item !== id))
+ : undefined;
+ const announcements: Announcements = {
+ onDragStart(id) {
+ return `Picked up sortable item ${id}. Sortable item ${id} is in position ${getPosition(
+ id,
+ )} of ${items.length}`;
+ },
+ onDragOver(id, overId) {
+ // In this specific use-case, the picked up item's `id` is always the same as the first `over` id.
+ // The first `onDragOver` event therefore doesn't need to be announced, because it is called
+ // immediately after the `onDragStart` announcement and is redundant.
+ if (isFirstAnnouncement.current === true) {
+ isFirstAnnouncement.current = false;
+ return;
+ }
+
+ if (overId) {
+ return `Sortable item ${id} was moved into position ${getPosition(
+ overId,
+ )} of ${items.length}`;
+ }
+
+ return;
+ },
+ onDragEnd(id, overId) {
+ if (overId) {
+ return `Sortable item ${id} was dropped at position ${getPosition(
+ overId,
+ )} of ${items.length}`;
+ }
+
+ return;
+ },
+ onDragCancel(id) {
+ return `Sorting was cancelled. Sortable item ${id} was dropped and returned to position ${getPosition(
+ id,
+ )} of ${items.length}.`;
+ },
+ };
+
+ useEffect(() => {
+ if (!activeId) {
+ isFirstAnnouncement.current = true;
+ }
+ }, [activeId]);
+
+ return (
+ {
+ if (!active) {
+ return;
+ }
+
+ setActiveId(active.id);
+ }}
+ onDragEnd={({ over }) => {
+ setActiveId(null);
+
+ if (over) {
+ const overIndex = getIndex(over.id);
+ if (activeIndex !== overIndex) {
+ setItems((items) => arrayMove(items, activeIndex, overIndex));
+ }
+ }
+ }}
+ onDragCancel={() => setActiveId(null)}
+ measuring={measuring}
+ modifiers={modifiers}
+ >
+
+
+
+ {items.map((value, index) => (
+
+ ))}
+
+
+
+ {useDragOverlay
+ ? createPortal(
+
+ {activeId ? (
+
+ ) : null}
+ ,
+ document.body,
+ )
+ : null}
+
+ );
+}
+
+interface SortableItemProps {
+ animateLayoutChanges?: AnimateLayoutChanges;
+ disabled?: boolean;
+ id: string;
+ index: number;
+ handle: boolean;
+ useDragOverlay?: boolean;
+ onRemove?(id: string): void;
+ style(values: any): React.CSSProperties;
+ renderItem?(args: any): React.ReactElement;
+ wrapperStyle({
+ index,
+ isDragging,
+ id,
+ }: {
+ index: number;
+ isDragging: boolean;
+ id: string;
+ }): React.CSSProperties;
+}
+
+export function SortableItem({
+ disabled,
+ animateLayoutChanges,
+ id,
+ index,
+ handle,
+ onRemove,
+ style,
+ renderItem,
+ useDragOverlay,
+ wrapperStyle,
+}: SortableItemProps) {
+ const {
+ attributes,
+ isDragging,
+ isSorting,
+ listeners,
+ overIndex,
+ setNodeRef,
+ transform,
+ transition,
+ } = useSortable({
+ animateLayoutChanges,
+ id,
+ disabled,
+ });
+
+ return (
+ - onRemove(id) : undefined}
+ transform={transform}
+ transition={!useDragOverlay && isDragging ? "none" : transition}
+ wrapperStyle={wrapperStyle({ index, isDragging, id })}
+ listeners={listeners}
+ data-index={index}
+ data-id={id}
+ dragOverlay={!useDragOverlay && isDragging}
+ {...attributes}
+ />
+ );
+}
diff --git a/src/client/components/SortableGrid/SortableGrid.tsx b/src/client/components/SortableGrid/SortableGrid.tsx
new file mode 100644
index 0000000..16eaf2e
--- /dev/null
+++ b/src/client/components/SortableGrid/SortableGrid.tsx
@@ -0,0 +1,141 @@
+import React from "react";
+import { MeasuringStrategy } from "@dnd-kit/core";
+import {
+ AnimateLayoutChanges,
+ defaultAnimateLayoutChanges,
+ rectSortingStrategy,
+} from "@dnd-kit/sortable";
+
+import { Sortable, Props as SortableProps } from "./Sortable";
+import { GridContainer } from "./components";
+
+export default {
+ title: "Presets/Sortable/Grid",
+};
+
+const props: Partial = {
+ adjustScale: true,
+ Container: (props: any) => ,
+ strategy: rectSortingStrategy,
+ wrapperStyle: () => ({
+ width: 140,
+ height: 140,
+ }),
+};
+
+export const BasicSetup = () => ;
+
+export const WithoutDragOverlay = () => (
+
+);
+
+export const LargeFirstTile = () => (
+ {
+ if (index === 0) {
+ return {
+ fontSize: "2rem",
+ padding: "36px 40px",
+ };
+ }
+
+ return {};
+ }}
+ wrapperStyle={({ index }) => {
+ if (index === 0) {
+ return {
+ height: 288,
+ gridRowStart: "span 2",
+ gridColumnStart: "span 2",
+ };
+ }
+
+ return {
+ width: 140,
+ height: 140,
+ };
+ }}
+ />
+);
+
+export const VariableSizes = () => (
+ {
+ if (index === 0 || index === 9) {
+ return {
+ fontSize: "2rem",
+ padding: "36px 40px",
+ };
+ }
+
+ return {};
+ }}
+ wrapperStyle={({ index }) => {
+ if (index === 0 || index === 9) {
+ return {
+ height: 288,
+ gridRowStart: "span 2",
+ gridColumnStart: "span 2",
+ };
+ }
+
+ return {
+ width: 140,
+ height: 140,
+ };
+ }}
+ />
+);
+
+export const DragHandle = () => ;
+
+export const ScrollContainer = () => (
+
+
+
+);
+
+export const PressDelay = () => (
+
+);
+
+export const MinimumDistance = () => (
+
+);
+
+export const RemovableItems = () => {
+ const animateLayoutChanges: AnimateLayoutChanges = (args) =>
+ args.isSorting || args.wasDragging
+ ? defaultAnimateLayoutChanges(args)
+ : true;
+
+ return (
+
+ );
+};
diff --git a/src/client/components/SortableGrid/components/Button/Button.module.css b/src/client/components/SortableGrid/components/Button/Button.module.css
new file mode 100644
index 0000000..f4b754b
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Button/Button.module.css
@@ -0,0 +1,27 @@
+.Button {
+ padding: 14px 20px;
+ border: none;
+ background-color: #242836;
+ border-radius: 8px;
+ font-size: 14px;
+ font-weight: 600;
+ color: #f6f8ff;
+ cursor: pointer;
+ outline: none;
+ transform: scale(1);
+ transition: transform 0.2s, background 0.4s;
+
+ &:hover,
+ &:focus {
+ background-color: #2f3545;
+ transform: scale(1.02);
+ }
+
+ &:focus {
+ box-shadow: 0 0 0 4px #4c9ffe;
+ }
+
+ &:active {
+ transform: scale(0.95);
+ }
+}
diff --git a/src/client/components/SortableGrid/components/Button/Button.module.css.d.ts b/src/client/components/SortableGrid/components/Button/Button.module.css.d.ts
new file mode 100644
index 0000000..1b17034
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Button/Button.module.css.d.ts
@@ -0,0 +1,7 @@
+// This file is automatically generated.
+// Please do not change this file!
+interface CssExports {
+ 'Button': string;
+}
+export const cssExports: CssExports;
+export default cssExports;
diff --git a/src/client/components/SortableGrid/components/Button/Button.tsx b/src/client/components/SortableGrid/components/Button/Button.tsx
new file mode 100644
index 0000000..1279ecf
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Button/Button.tsx
@@ -0,0 +1,16 @@
+import React, {HTMLAttributes} from 'react';
+import classNames from 'classnames';
+
+import styles from './Button.module.css';
+
+export interface Props extends HTMLAttributes {
+ children: React.ReactNode;
+}
+
+export function Button({children, ...props}: Props) {
+ return (
+
+ );
+}
diff --git a/src/client/components/SortableGrid/components/Button/index.ts b/src/client/components/SortableGrid/components/Button/index.ts
new file mode 100644
index 0000000..2cdeff8
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Button/index.ts
@@ -0,0 +1 @@
+export {Button} from './Button';
diff --git a/src/client/components/SortableGrid/components/ConfirmModal/ConfirmModal.module.css b/src/client/components/SortableGrid/components/ConfirmModal/ConfirmModal.module.css
new file mode 100644
index 0000000..a6c7898
--- /dev/null
+++ b/src/client/components/SortableGrid/components/ConfirmModal/ConfirmModal.module.css
@@ -0,0 +1,42 @@
+.ConfirmModal {
+ --width: 250px;
+ --height: 120px;
+
+ display: flex;
+ flex-direction: column;
+ width: var(--width);
+ height: var(--height);
+ position: fixed;
+ top: calc(100vh / 2 - var(--height) / 2);
+ left: calc(100vw / 2 - var(--width) / 2);
+ border-radius: 10px;
+ box-shadow: -1px 0 15px 0 rgba(34, 33, 81, 0.01),
+ 0px 15px 15px 0 rgba(34, 33, 81, 0.25);
+ padding: 15px;
+ box-sizing: border-box;
+ background-color: white;
+ text-align: center;
+ z-index: 1;
+
+ h1 {
+ flex: 1;
+ font-size: 16px;
+ font-weight: normal;
+ line-height: 20px;
+ }
+
+ button {
+ width: 50px;
+ height: 23px;
+ background: #242836;
+ color: #f6f8ff;
+ border: none;
+ border-radius: 3px;
+ margin: 0 5px;
+ cursor: pointer;
+
+ &:hover {
+ background-color: #2f3545;
+ }
+ }
+}
diff --git a/src/client/components/SortableGrid/components/ConfirmModal/ConfirmModal.module.css.d.ts b/src/client/components/SortableGrid/components/ConfirmModal/ConfirmModal.module.css.d.ts
new file mode 100644
index 0000000..92ad301
--- /dev/null
+++ b/src/client/components/SortableGrid/components/ConfirmModal/ConfirmModal.module.css.d.ts
@@ -0,0 +1,7 @@
+// This file is automatically generated.
+// Please do not change this file!
+interface CssExports {
+ 'ConfirmModal': string;
+}
+export const cssExports: CssExports;
+export default cssExports;
diff --git a/src/client/components/SortableGrid/components/ConfirmModal/ConfirmModal.tsx b/src/client/components/SortableGrid/components/ConfirmModal/ConfirmModal.tsx
new file mode 100644
index 0000000..99e297b
--- /dev/null
+++ b/src/client/components/SortableGrid/components/ConfirmModal/ConfirmModal.tsx
@@ -0,0 +1,21 @@
+import React, {PropsWithChildren} from 'react';
+import styles from './ConfirmModal.module.css';
+
+interface Props {
+ onConfirm(): void;
+ onDeny(): void;
+}
+
+export const ConfirmModal = ({
+ onConfirm,
+ onDeny,
+ children,
+}: PropsWithChildren) => (
+
+
{children}
+
+
+
+
+
+);
diff --git a/src/client/components/SortableGrid/components/ConfirmModal/index.ts b/src/client/components/SortableGrid/components/ConfirmModal/index.ts
new file mode 100644
index 0000000..1027bee
--- /dev/null
+++ b/src/client/components/SortableGrid/components/ConfirmModal/index.ts
@@ -0,0 +1 @@
+export {ConfirmModal} from './ConfirmModal';
diff --git a/src/client/components/SortableGrid/components/Container/Container.module.css b/src/client/components/SortableGrid/components/Container/Container.module.css
new file mode 100644
index 0000000..1fd675f
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Container/Container.module.css
@@ -0,0 +1,103 @@
+.Container {
+ display: flex;
+ flex-direction: column;
+ grid-auto-rows: max-content;
+ overflow: hidden;
+ box-sizing: border-box;
+ appearance: none;
+ outline: none;
+ min-width: 350px;
+ margin: 10px;
+ border-radius: 5px;
+ min-height: 200px;
+ transition: background-color 350ms ease;
+ background-color: rgba(246, 246, 246, 1);
+ border: 1px solid rgba(0, 0, 0, 0.05);
+ font-size: 1em;
+
+ ul {
+ display: grid;
+ grid-gap: 10px;
+ grid-template-columns: repeat(var(--columns, 1), 1fr);
+ list-style: none;
+ padding: 20px;
+ margin: 0;
+ }
+
+ &.scrollable {
+ ul {
+ overflow-y: auto;
+ }
+ }
+
+ &.placeholder {
+ justify-content: center;
+ align-items: center;
+ cursor: pointer;
+ color: rgba(0, 0, 0, 0.5);
+ background-color: transparent;
+ border-style: dashed;
+ border-color: rgba(0, 0, 0, 0.08);
+
+ &:hover {
+ border-color: rgba(0, 0, 0, 0.15);
+ }
+ }
+
+ &.hover {
+ background-color: rgb(235, 235, 235, 1);
+ }
+
+ &.unstyled {
+ overflow: visible;
+ background-color: transparent !important;
+ border: none !important;
+ }
+
+ &.horizontal {
+ width: 100%;
+
+ ul {
+ grid-auto-flow: column;
+ }
+ }
+
+ &.shadow {
+ box-shadow: 0 1px 10px 0 rgba(34, 33, 81, 0.1);
+ }
+
+ &:focus-visible {
+ border-color: transparent;
+ box-shadow: 0 0 0 2px rgba(255, 255, 255, 0), 0 0px 0px 2px #4c9ffe;
+ }
+}
+
+.Header {
+ display: flex;
+ padding: 5px 20px;
+ padding-right: 8px;
+ align-items: center;
+ justify-content: space-between;
+ background-color: #fff;
+ border-top-left-radius: 5px;
+ border-top-right-radius: 5px;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.08);
+
+ &:hover {
+ .Actions > * {
+ opacity: 1 !important;
+ }
+ }
+}
+
+.Actions {
+ display: flex;
+
+ > *:first-child:not(:last-child) {
+ opacity: 0;
+
+ &:focus-visible {
+ opacity: 1;
+ }
+ }
+}
diff --git a/src/client/components/SortableGrid/components/Container/Container.module.css.d.ts b/src/client/components/SortableGrid/components/Container/Container.module.css.d.ts
new file mode 100644
index 0000000..067ff72
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Container/Container.module.css.d.ts
@@ -0,0 +1,15 @@
+// This file is automatically generated.
+// Please do not change this file!
+interface CssExports {
+ 'Actions': string;
+ 'Container': string;
+ 'Header': string;
+ 'horizontal': string;
+ 'hover': string;
+ 'placeholder': string;
+ 'scrollable': string;
+ 'shadow': string;
+ 'unstyled': string;
+}
+export const cssExports: CssExports;
+export default cssExports;
diff --git a/src/client/components/SortableGrid/components/Container/Container.tsx b/src/client/components/SortableGrid/components/Container/Container.tsx
new file mode 100644
index 0000000..d48b904
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Container/Container.tsx
@@ -0,0 +1,81 @@
+import React, {forwardRef} from 'react';
+import classNames from 'classnames';
+
+import {Handle, Remove} from '../Item';
+
+import styles from './Container.module.css';
+
+export interface Props {
+ children: React.ReactNode;
+ columns?: number;
+ label?: string;
+ style?: React.CSSProperties;
+ horizontal?: boolean;
+ hover?: boolean;
+ handleProps?: React.HTMLAttributes;
+ scrollable?: boolean;
+ shadow?: boolean;
+ placeholder?: boolean;
+ unstyled?: boolean;
+ onClick?(): void;
+ onRemove?(): void;
+}
+
+export const Container = forwardRef(
+ (
+ {
+ children,
+ columns = 1,
+ handleProps,
+ horizontal,
+ hover,
+ onClick,
+ onRemove,
+ label,
+ placeholder,
+ style,
+ scrollable,
+ shadow,
+ unstyled,
+ ...props
+ }: Props,
+ ref
+ ) => {
+ const Component = onClick ? 'button' : 'div';
+
+ return (
+
+ {label ? (
+
+ {label}
+
+ {onRemove ? : undefined}
+
+
+
+ ) : null}
+ {placeholder ? children : }
+
+ );
+ }
+);
diff --git a/src/client/components/SortableGrid/components/Container/index.ts b/src/client/components/SortableGrid/components/Container/index.ts
new file mode 100644
index 0000000..e8236dc
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Container/index.ts
@@ -0,0 +1,2 @@
+export {Container} from './Container';
+export type {Props as ContainerProps} from './Container';
diff --git a/src/client/components/SortableGrid/components/Draggable.tsx b/src/client/components/SortableGrid/components/Draggable.tsx
new file mode 100644
index 0000000..599915e
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Draggable.tsx
@@ -0,0 +1,21 @@
+import React from "react";
+import { useDraggable } from "@dnd-kit/core";
+
+export const Draggable = (props) => {
+ const { attributes, listeners, setNodeRef, transform } = useDraggable({
+ id: "draggable",
+ });
+ const style = transform
+ ? {
+ transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`,
+ }
+ : undefined;
+
+ return (
+
+ );
+};
+
+export default Draggable;
diff --git a/src/client/components/SortableGrid/components/Draggable/Draggable.module.css b/src/client/components/SortableGrid/components/Draggable/Draggable.module.css
new file mode 100644
index 0000000..ee5bb0c
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Draggable/Draggable.module.css
@@ -0,0 +1,128 @@
+.Draggable {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ transition: transform 250ms ease;
+ transform: translate3d(var(--translate-x, 0), var(--translate-y, 0), 0);
+
+ button {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin: 0 auto;
+ min-height: 54px;
+ flex-shrink: 1;
+ appearance: none;
+ outline: none;
+ border: 0;
+ padding: 8px 18px;
+ background-color: #181a22;
+ border-radius: 5px;
+ box-shadow: var(--box-shadow);
+ transform: scale(var(--scale, 1));
+ transition: transform 250ms cubic-bezier(0.18, 0.67, 0.6, 1.22),
+ box-shadow 300ms ease;
+ }
+
+ &:not(.handle) {
+ button {
+ touch-action: none;
+ cursor: grab;
+
+ &:focus-visible:not(.active &) {
+ box-shadow: 0 0 0 3px #4c9ffe;
+ }
+ }
+ }
+
+ &.handle {
+ button {
+ --action-background: rgba(255, 255, 255, 0.1);
+
+ > svg {
+ margin-right: 5px;
+ }
+
+ > button {
+ margin-right: -10px;
+ }
+ }
+ }
+
+ img {
+ width: 140px;
+ user-select: none;
+ pointer-events: none;
+ }
+
+ label {
+ display: block;
+ flex-shrink: 1;
+ padding: 10px;
+ transition: opacity 250ms ease;
+
+ text-align: center;
+ font-size: 1rem;
+ font-weight: 300;
+ color: #8d8d8d;
+ user-select: none;
+ cursor: url('/cursor.svg'), auto;
+
+ animation-name: pulse;
+ animation-duration: 1.5s;
+ animation-delay: 2s;
+ animation-iteration-count: infinite;
+ animation-timing-function: ease;
+ animation-direction: alternate;
+ }
+
+ &.dragging {
+ z-index: 1;
+ transition: none;
+
+ * {
+ cursor: grabbing;
+ }
+
+ button {
+ --scale: 1.06;
+ --box-shadow: -1px 0 15px 0 rgba(34, 33, 81, 0.01),
+ 0px 15px 15px 0 rgba(34, 33, 81, 0.25);
+
+ &:focus-visible {
+ --box-shadow: 0 0px 10px 2px #4c9ffe;
+ }
+ }
+
+ label {
+ animation: none;
+ opacity: 0;
+ }
+ }
+
+ &.dragOverlay {
+ button {
+ animation: pop 250ms cubic-bezier(0.18, 0.67, 0.6, 1.22);
+ }
+ }
+}
+
+@keyframes pulse {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+
+@keyframes pop {
+ 0% {
+ transform: scale(1);
+ }
+ 100% {
+ transform: scale(var(--scale));
+ box-shadow: var(--box-shadow);
+ }
+}
diff --git a/src/client/components/SortableGrid/components/Draggable/Draggable.tsx b/src/client/components/SortableGrid/components/Draggable/Draggable.tsx
new file mode 100644
index 0000000..c2645c8
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Draggable/Draggable.tsx
@@ -0,0 +1,79 @@
+import React, {forwardRef} from 'react';
+import classNames from 'classnames';
+import type {DraggableSyntheticListeners, Translate} from '@dnd-kit/core';
+
+import {Handle} from '../Item/components/Handle';
+
+import {
+ draggable,
+ draggableHorizontal,
+ draggableVertical,
+} from './draggable-svg';
+import styles from './Draggable.module.css';
+
+export enum Axis {
+ All,
+ Vertical,
+ Horizontal,
+}
+
+interface Props {
+ axis?: Axis;
+ dragOverlay?: boolean;
+ dragging?: boolean;
+ handle?: boolean;
+ label?: string;
+ listeners?: DraggableSyntheticListeners;
+ style?: React.CSSProperties;
+ translate?: Translate;
+}
+
+export const Draggable = forwardRef(
+ function Draggable(
+ {
+ axis,
+ dragOverlay,
+ dragging,
+ handle,
+ label,
+ listeners,
+ translate,
+ ...props
+ },
+ ref
+ ) {
+ return (
+
+
+ {label ? : null}
+
+ );
+ }
+);
diff --git a/src/client/components/SortableGrid/components/Draggable/draggable-svg.tsx b/src/client/components/SortableGrid/components/Draggable/draggable-svg.tsx
new file mode 100644
index 0000000..3e5f294
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Draggable/draggable-svg.tsx
@@ -0,0 +1,48 @@
+import React from 'react';
+
+export const draggable = (
+
+);
+
+export const draggableVertical = (
+
+);
+
+export const draggableHorizontal = (
+
+);
diff --git a/src/client/components/SortableGrid/components/Draggable/index.ts b/src/client/components/SortableGrid/components/Draggable/index.ts
new file mode 100644
index 0000000..0f94ad3
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Draggable/index.ts
@@ -0,0 +1 @@
+export { Axis, Draggable } from "./Draggable";
diff --git a/src/client/components/SortableGrid/components/Droppable.tsx b/src/client/components/SortableGrid/components/Droppable.tsx
new file mode 100644
index 0000000..d427bb1
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Droppable.tsx
@@ -0,0 +1,19 @@
+import React from "react";
+import { useDroppable } from "@dnd-kit/core";
+
+export const Droppable = (props) => {
+ const { isOver, setNodeRef } = useDroppable({
+ id: "droppable",
+ });
+ const style = {
+ color: isOver ? "green" : undefined,
+ };
+
+ return (
+
+ {props.children}
+
+ );
+};
+
+export default Droppable;
diff --git a/src/client/components/SortableGrid/components/Droppable/Droppable.module.css b/src/client/components/SortableGrid/components/Droppable/Droppable.module.css
new file mode 100644
index 0000000..70e77c9
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Droppable/Droppable.module.css
@@ -0,0 +1,51 @@
+.Droppable {
+ position: relative;
+ padding-top: 80px;
+ text-align: center;
+ border-radius: 10px;
+ width: 340px;
+ height: 340px;
+ box-sizing: border-box;
+ background-color: #fff;
+ box-shadow: inset rgba(201, 211, 219, 0.5) 0 0 0 2px,
+ rgba(255, 255, 255, 0) 0 0 0 1px, rgba(201, 211, 219, 0.25) 20px 14px 24px;
+ transition: box-shadow 250ms ease;
+
+ > svg {
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ width: 200px;
+ transform: translate3d(-50%, -50%, 0);
+ opacity: 0.8;
+ transition: opacity 300ms ease, transform 200ms ease;
+ user-select: none;
+ pointer-events: none;
+ }
+
+ &.dragging {
+ > svg {
+ opacity: 0.8;
+ }
+ }
+
+ &.over {
+ box-shadow: inset #1eb99d 0 0 0 3px, rgba(201, 211, 219, 0.5) 20px 14px 24px;
+
+ > svg {
+ opacity: 1;
+ }
+
+ &.dropped {
+ box-shadow: inset rgba(201, 211, 219, 0.7) 0 0 0 3px,
+ rgba(201, 211, 219, 0.5) 20px 14px 24px;
+ }
+ }
+
+ &.dropped {
+ > svg {
+ opacity: 0.2;
+ transform: translate3d(-50%, 100%, 0) scale(0.8);
+ }
+ }
+}
diff --git a/src/client/components/SortableGrid/components/Droppable/Droppable.tsx b/src/client/components/SortableGrid/components/Droppable/Droppable.tsx
new file mode 100644
index 0000000..e3f480a
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Droppable/Droppable.tsx
@@ -0,0 +1,34 @@
+import React from 'react';
+import {useDroppable, UniqueIdentifier} from '@dnd-kit/core';
+import classNames from 'classnames';
+
+import {droppable} from './droppable-svg';
+import styles from './Droppable.module.css';
+
+interface Props {
+ children: React.ReactNode;
+ dragging: boolean;
+ id: UniqueIdentifier;
+}
+
+export function Droppable({children, id, dragging}: Props) {
+ const {isOver, setNodeRef} = useDroppable({
+ id,
+ });
+
+ return (
+
+ {children}
+ {droppable}
+
+ );
+}
diff --git a/src/client/components/SortableGrid/components/Droppable/droppable-svg.tsx b/src/client/components/SortableGrid/components/Droppable/droppable-svg.tsx
new file mode 100644
index 0000000..e1b3e27
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Droppable/droppable-svg.tsx
@@ -0,0 +1,18 @@
+import React from 'react';
+
+export const droppable = (
+
+);
diff --git a/src/client/components/SortableGrid/components/Droppable/index.ts b/src/client/components/SortableGrid/components/Droppable/index.ts
new file mode 100644
index 0000000..a9eddaa
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Droppable/index.ts
@@ -0,0 +1 @@
+export {Droppable} from './Droppable';
diff --git a/src/client/components/SortableGrid/components/DroppableGrid.tsx b/src/client/components/SortableGrid/components/DroppableGrid.tsx
new file mode 100644
index 0000000..5ad37dc
--- /dev/null
+++ b/src/client/components/SortableGrid/components/DroppableGrid.tsx
@@ -0,0 +1,35 @@
+import React, { useState } from "react";
+import { DndContext } from "@dnd-kit/core";
+
+import { Draggable } from "./Draggable";
+import { Droppable } from "./Droppable";
+
+export const DroppableGrid = () => {
+ const containers = ["A", "B", "C"];
+ const [parent, setParent] = useState(null);
+ const draggableMarkup = Drag me;
+
+ return (
+
+ {parent === null ? draggableMarkup : null}
+
+ {containers.map((id) => (
+ // We updated the Droppable component so it would accept an `id`
+ // prop and pass it to `useDroppable`
+
+ {parent === id ? draggableMarkup : "Drop here"}
+
+ ))}
+
+ );
+
+ function handleDragEnd(event) {
+ const { over } = event;
+
+ // If the item is dropped over a container, set it as the parent
+ // otherwise reset the parent to `null`
+ setParent(over ? over.id : null);
+ }
+};
+
+export default DroppableGrid;
diff --git a/src/client/components/SortableGrid/components/FloatingControls/FloatingControls.module.css b/src/client/components/SortableGrid/components/FloatingControls/FloatingControls.module.css
new file mode 100644
index 0000000..cf8dea8
--- /dev/null
+++ b/src/client/components/SortableGrid/components/FloatingControls/FloatingControls.module.css
@@ -0,0 +1,5 @@
+.FloatingControls {
+ position: fixed;
+ top: 25px;
+ right: 25px;
+}
diff --git a/src/client/components/SortableGrid/components/FloatingControls/FloatingControls.module.css.d.ts b/src/client/components/SortableGrid/components/FloatingControls/FloatingControls.module.css.d.ts
new file mode 100644
index 0000000..55802f2
--- /dev/null
+++ b/src/client/components/SortableGrid/components/FloatingControls/FloatingControls.module.css.d.ts
@@ -0,0 +1,7 @@
+// This file is automatically generated.
+// Please do not change this file!
+interface CssExports {
+ 'FloatingControls': string;
+}
+export const cssExports: CssExports;
+export default cssExports;
diff --git a/src/client/components/SortableGrid/components/FloatingControls/FloatingControls.tsx b/src/client/components/SortableGrid/components/FloatingControls/FloatingControls.tsx
new file mode 100644
index 0000000..d504c4d
--- /dev/null
+++ b/src/client/components/SortableGrid/components/FloatingControls/FloatingControls.tsx
@@ -0,0 +1,12 @@
+import React from 'react';
+import classNames from 'classnames';
+
+import styles from './FloatingControls.module.css';
+
+export interface Props {
+ children: React.ReactNode;
+}
+
+export function FloatingControls({children}: Props) {
+ return {children}
;
+}
diff --git a/src/client/components/SortableGrid/components/FloatingControls/index.ts b/src/client/components/SortableGrid/components/FloatingControls/index.ts
new file mode 100644
index 0000000..efa7417
--- /dev/null
+++ b/src/client/components/SortableGrid/components/FloatingControls/index.ts
@@ -0,0 +1 @@
+export {FloatingControls} from './FloatingControls';
diff --git a/src/client/components/SortableGrid/components/Grid/Grid.module.css b/src/client/components/SortableGrid/components/Grid/Grid.module.css
new file mode 100644
index 0000000..31c1c09
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Grid/Grid.module.css
@@ -0,0 +1,30 @@
+.Grid {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-image: repeating-linear-gradient(
+ 0deg,
+ transparent,
+ transparent calc(var(--grid-size) - 1px),
+ #ddd calc(var(--grid-size) - 1px),
+ #ddd var(--grid-size)
+ ),
+ repeating-linear-gradient(
+ -90deg,
+ transparent,
+ transparent calc(var(--grid-size) - 1px),
+ #ddd calc(var(--grid-size) - 1px),
+ #ddd var(--grid-size)
+ );
+ background-size: var(--grid-size) var(--grid-size);
+ z-index: -1;
+ pointer-events: none;
+}
+
+.RangeSlider {
+ position: fixed;
+ right: 20px;
+ bottom: 20px;
+}
diff --git a/src/client/components/SortableGrid/components/Grid/Grid.module.css.d.ts b/src/client/components/SortableGrid/components/Grid/Grid.module.css.d.ts
new file mode 100644
index 0000000..2e04c36
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Grid/Grid.module.css.d.ts
@@ -0,0 +1,8 @@
+// This file is automatically generated.
+// Please do not change this file!
+interface CssExports {
+ 'Grid': string;
+ 'RangeSlider': string;
+}
+export const cssExports: CssExports;
+export default cssExports;
diff --git a/src/client/components/SortableGrid/components/Grid/Grid.tsx b/src/client/components/SortableGrid/components/Grid/Grid.tsx
new file mode 100644
index 0000000..20d8216
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Grid/Grid.tsx
@@ -0,0 +1,22 @@
+import React from 'react';
+
+import styles from './Grid.module.css';
+
+export interface Props {
+ size: number;
+ step?: number;
+ onSizeChange(size: number): void;
+}
+
+export function Grid({size}: Props) {
+ return (
+
+ );
+}
diff --git a/src/client/components/SortableGrid/components/Grid/index.ts b/src/client/components/SortableGrid/components/Grid/index.ts
new file mode 100644
index 0000000..efd9c71
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Grid/index.ts
@@ -0,0 +1 @@
+export {Grid} from './Grid';
diff --git a/src/client/components/SortableGrid/components/GridContainer/GridContainer.module.css b/src/client/components/SortableGrid/components/GridContainer/GridContainer.module.css
new file mode 100644
index 0000000..3e1be88
--- /dev/null
+++ b/src/client/components/SortableGrid/components/GridContainer/GridContainer.module.css
@@ -0,0 +1,15 @@
+.GridContainer {
+ max-width: 800px;
+ display: grid;
+ grid-template-columns: repeat(var(--col-count), 1fr);
+ grid-gap: 10px;
+ padding: 20px;
+
+ @media (max-width: 850px) {
+ grid-template-columns: repeat(calc(var(--col-count) - 1), 1fr);
+ }
+
+ @media (max-width: 650px) {
+ grid-template-columns: repeat(calc(var(--col-count) - 2), 1fr);
+ }
+}
diff --git a/src/client/components/SortableGrid/components/GridContainer/GridContainer.module.css.d.ts b/src/client/components/SortableGrid/components/GridContainer/GridContainer.module.css.d.ts
new file mode 100644
index 0000000..2c401e4
--- /dev/null
+++ b/src/client/components/SortableGrid/components/GridContainer/GridContainer.module.css.d.ts
@@ -0,0 +1,7 @@
+// This file is automatically generated.
+// Please do not change this file!
+interface CssExports {
+ 'GridContainer': string;
+}
+export const cssExports: CssExports;
+export default cssExports;
diff --git a/src/client/components/SortableGrid/components/GridContainer/GridContainer.tsx b/src/client/components/SortableGrid/components/GridContainer/GridContainer.tsx
new file mode 100644
index 0000000..8cfe184
--- /dev/null
+++ b/src/client/components/SortableGrid/components/GridContainer/GridContainer.tsx
@@ -0,0 +1,23 @@
+import React from 'react';
+
+import styles from './GridContainer.module.css';
+
+export interface Props {
+ children: React.ReactNode;
+ columns: number;
+}
+
+export function GridContainer({children, columns}: Props) {
+ return (
+
+ );
+}
diff --git a/src/client/components/SortableGrid/components/GridContainer/index.ts b/src/client/components/SortableGrid/components/GridContainer/index.ts
new file mode 100644
index 0000000..063c01e
--- /dev/null
+++ b/src/client/components/SortableGrid/components/GridContainer/index.ts
@@ -0,0 +1 @@
+export {GridContainer} from './GridContainer';
diff --git a/src/client/components/SortableGrid/components/Item/Item.module.css b/src/client/components/SortableGrid/components/Item/Item.module.css
new file mode 100644
index 0000000..25581dd
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Item/Item.module.css
@@ -0,0 +1,145 @@
+$font-weight: 400;
+$background-color: #000;
+$border-color: #efefef;
+$text-color: #333;
+$handle-color: rgba(0, 0, 0, 0.25);
+$box-shadow-border: 0 0 0 calc(1px / var(--scale-x, 1)) rgba(63, 63, 68, 0.05);
+$box-shadow-common: 0 1px calc(3px / var(--scale-x, 1)) 0 rgba(34, 33, 81, 0.15);
+$box-shadow: $box-shadow-border, $box-shadow-common;
+$focused-outline-color: #4c9ffe;
+
+@keyframes pop {
+ 0% {
+ transform: scale(1);
+ box-shadow: var(--box-shadow);
+ }
+ 100% {
+ transform: scale(var(--scale));
+ box-shadow: var(--box-shadow-picked-up);
+ }
+}
+
+@keyframes fadeIn {
+ 0% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
+
+.Wrapper {
+ display: flex;
+ box-sizing: border-box;
+ transform: translate3d(var(--translate-x, 0), var(--translate-y, 0), 0)
+ scaleX(var(--scale-x, 1)) scaleY(var(--scale-y, 1));
+ transform-origin: 0 0;
+ touch-action: manipulation;
+
+ &.fadeIn {
+ animation: fadeIn 500ms ease;
+ }
+
+ &.dragOverlay {
+ --scale: 1.05;
+ --box-shadow: $box-shadow;
+ --box-shadow-picked-up: $box-shadow-border,
+ -1px 0 15px 0 rgba(34, 33, 81, 0.01),
+ 0px 15px 15px 0 rgba(34, 33, 81, 0.25);
+ z-index: 999;
+ }
+}
+
+.Item {
+ position: relative;
+ display: flex;
+ flex-grow: 1;
+ align-items: center;
+ padding: 18px 20px;
+ background-color: #ddd;
+ box-shadow: $box-shadow;
+ outline: none;
+ border-radius: calc(4px / var(--scale-x, 1));
+ box-sizing: border-box;
+ list-style: none;
+ transform-origin: 50% 50%;
+
+ -webkit-tap-highlight-color: transparent;
+
+ color: $text-color;
+ font-weight: $font-weight;
+ font-size: 1rem;
+ white-space: nowrap;
+
+ transform: scale(var(--scale, 1));
+ transition: box-shadow 200ms cubic-bezier(0.18, 0.67, 0.6, 1.22);
+
+ &:focus-visible {
+ box-shadow: 0 0px 4px 1px $focused-outline-color, $box-shadow;
+ }
+
+ &:not(.withHandle) {
+ touch-action: manipulation;
+ cursor: grab;
+ }
+
+ &.dragging:not(.dragOverlay) {
+ opacity: var(--dragging-opacity, 0.5);
+ z-index: 0;
+
+ &:focus {
+ box-shadow: $box-shadow;
+ }
+ }
+
+ &.disabled {
+ color: #999;
+ background-color: #f1f1f1;
+ &:focus {
+ box-shadow: 0 0px 4px 1px rgba(0, 0, 0, 0.1), $box-shadow;
+ }
+ cursor: not-allowed;
+ }
+
+ &.dragOverlay {
+ cursor: inherit;
+ /* box-shadow: 0 0px 6px 2px $focused-outline-color; */
+ animation: pop 200ms cubic-bezier(0.18, 0.67, 0.6, 1.22);
+ transform: scale(var(--scale));
+ box-shadow: var(--box-shadow-picked-up);
+ opacity: 1;
+ }
+
+ &.color:before {
+ content: '';
+ position: absolute;
+ top: 50%;
+ transform: translateY(-50%);
+ left: 0;
+ height: 100%;
+ width: 3px;
+ display: block;
+ border-top-left-radius: 3px;
+ border-bottom-left-radius: 3px;
+ background-color: var(--color);
+ }
+
+ &:hover {
+ .Remove {
+ visibility: visible;
+ }
+ }
+}
+
+.Remove {
+ visibility: hidden;
+}
+
+.Actions {
+ display: flex;
+ align-self: flex-start;
+ margin-top: -12px;
+ margin-left: auto;
+ margin-bottom: -15px;
+ margin-right: -10px;
+}
diff --git a/src/client/components/SortableGrid/components/Item/Item.tsx b/src/client/components/SortableGrid/components/Item/Item.tsx
new file mode 100644
index 0000000..dcb55f4
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Item/Item.tsx
@@ -0,0 +1,150 @@
+import React, { useEffect } from "react";
+import classNames from "classnames";
+import type { DraggableSyntheticListeners } from "@dnd-kit/core";
+import type { Transform } from "@dnd-kit/utilities";
+
+import { Handle, Remove } from "./components";
+
+import styles from "./Item.module.css";
+
+export interface Props {
+ dragOverlay?: boolean;
+ color?: string;
+ disabled?: boolean;
+ dragging?: boolean;
+ handle?: boolean;
+ height?: number;
+ index?: number;
+ fadeIn?: boolean;
+ transform?: Transform | null;
+ listeners?: DraggableSyntheticListeners;
+ sorting?: boolean;
+ style?: React.CSSProperties;
+ transition?: string | null;
+ wrapperStyle?: React.CSSProperties;
+ value: React.ReactNode;
+ onRemove?(): void;
+ renderItem?(args: {
+ dragOverlay: boolean;
+ dragging: boolean;
+ sorting: boolean;
+ index: number | undefined;
+ fadeIn: boolean;
+ listeners: DraggableSyntheticListeners;
+ ref: React.Ref;
+ style: React.CSSProperties | undefined;
+ transform: Props["transform"];
+ transition: Props["transition"];
+ value: Props["value"];
+ }): React.ReactElement;
+}
+
+export const Item = React.memo(
+ React.forwardRef(
+ (
+ {
+ color,
+ dragOverlay,
+ dragging,
+ disabled,
+ fadeIn,
+ handle,
+ height,
+ index,
+ listeners,
+ onRemove,
+ renderItem,
+ sorting,
+ style,
+ transition,
+ transform,
+ value,
+ wrapperStyle,
+ ...props
+ },
+ ref,
+ ) => {
+ useEffect(() => {
+ if (!dragOverlay) {
+ return;
+ }
+
+ document.body.style.cursor = "grabbing";
+
+ return () => {
+ document.body.style.cursor = "";
+ };
+ }, [dragOverlay]);
+
+ return renderItem ? (
+ renderItem({
+ dragOverlay: Boolean(dragOverlay),
+ dragging: Boolean(dragging),
+ sorting: Boolean(sorting),
+ index,
+ fadeIn: Boolean(fadeIn),
+ listeners,
+ ref,
+ style,
+ transform,
+ transition,
+ value,
+ })
+ ) : (
+
+
+ {value}
+
+ {onRemove ? (
+
+ ) : null}
+ {handle ? : null}
+
+
+
+ );
+ },
+ ),
+);
diff --git a/src/client/components/SortableGrid/components/Item/components/Action/Action.module.css b/src/client/components/SortableGrid/components/Item/components/Action/Action.module.css
new file mode 100644
index 0000000..8fa6bfc
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Item/components/Action/Action.module.css
@@ -0,0 +1,50 @@
+$focused-outline-color: #4c9ffe;
+
+.Action {
+ display: flex;
+ width: 12px;
+ padding: 15px;
+ align-items: center;
+ justify-content: center;
+ flex: 0 0 auto;
+ touch-action: none;
+ cursor: var(--cursor, pointer);
+ border-radius: 5px;
+ border: none;
+ outline: none;
+ appearance: none;
+ background-color: transparent;
+ -webkit-tap-highlight-color: transparent;
+
+ @media (hover: hover) {
+ &:hover {
+ background-color: var(--action-background, rgba(0, 0, 0, 0.05));
+
+ svg {
+ fill: #6f7b88;
+ }
+ }
+ }
+
+ svg {
+ flex: 0 0 auto;
+ margin: auto;
+ height: 100%;
+ overflow: visible;
+ fill: #919eab;
+ }
+
+ &:active {
+ background-color: var(--background, rgba(0, 0, 0, 0.05));
+
+ svg {
+ fill: var(--fill, #788491);
+ }
+ }
+
+ &:focus-visible {
+ outline: none;
+ box-shadow: 0 0 0 2px rgba(255, 255, 255, 0),
+ 0 0px 0px 2px $focused-outline-color;
+ }
+}
diff --git a/src/client/components/SortableGrid/components/Item/components/Action/Action.module.css.d.ts b/src/client/components/SortableGrid/components/Item/components/Action/Action.module.css.d.ts
new file mode 100644
index 0000000..73364ab
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Item/components/Action/Action.module.css.d.ts
@@ -0,0 +1,7 @@
+// This file is automatically generated.
+// Please do not change this file!
+interface CssExports {
+ 'Action': string;
+}
+export const cssExports: CssExports;
+export default cssExports;
diff --git a/src/client/components/SortableGrid/components/Item/components/Action/Action.tsx b/src/client/components/SortableGrid/components/Item/components/Action/Action.tsx
new file mode 100644
index 0000000..5faee68
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Item/components/Action/Action.tsx
@@ -0,0 +1,30 @@
+import React, {CSSProperties} from 'react';
+import classNames from 'classnames';
+
+import styles from './Action.module.css';
+
+export interface Props extends React.HTMLAttributes {
+ active?: {
+ fill: string;
+ background: string;
+ };
+ cursor?: CSSProperties['cursor'];
+}
+
+export function Action({active, className, cursor, style, ...props}: Props) {
+ return (
+
+ );
+}
diff --git a/src/client/components/SortableGrid/components/Item/components/Action/index.ts b/src/client/components/SortableGrid/components/Item/components/Action/index.ts
new file mode 100644
index 0000000..1eb88ec
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Item/components/Action/index.ts
@@ -0,0 +1,2 @@
+export {Action} from './Action';
+export type {Props as ActionProps} from './Action';
diff --git a/src/client/components/SortableGrid/components/Item/components/Handle/Handle.tsx b/src/client/components/SortableGrid/components/Item/components/Handle/Handle.tsx
new file mode 100644
index 0000000..7e8af75
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Item/components/Handle/Handle.tsx
@@ -0,0 +1,13 @@
+import React from 'react';
+
+import {Action, ActionProps} from '../Action';
+
+export function Handle(props: ActionProps) {
+ return (
+
+
+
+ );
+}
diff --git a/src/client/components/SortableGrid/components/Item/components/Handle/index.ts b/src/client/components/SortableGrid/components/Item/components/Handle/index.ts
new file mode 100644
index 0000000..5c02d9e
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Item/components/Handle/index.ts
@@ -0,0 +1 @@
+export {Handle} from './Handle';
diff --git a/src/client/components/SortableGrid/components/Item/components/Remove/Remove.tsx b/src/client/components/SortableGrid/components/Item/components/Remove/Remove.tsx
new file mode 100644
index 0000000..3ed58c2
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Item/components/Remove/Remove.tsx
@@ -0,0 +1,19 @@
+import React from 'react';
+
+import {Action, ActionProps} from '../Action';
+
+export function Remove(props: ActionProps) {
+ return (
+
+
+
+ );
+}
diff --git a/src/client/components/SortableGrid/components/Item/components/Remove/index.ts b/src/client/components/SortableGrid/components/Item/components/Remove/index.ts
new file mode 100644
index 0000000..ebf8d9f
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Item/components/Remove/index.ts
@@ -0,0 +1 @@
+export {Remove} from './Remove';
diff --git a/src/client/components/SortableGrid/components/Item/components/index.ts b/src/client/components/SortableGrid/components/Item/components/index.ts
new file mode 100644
index 0000000..e699912
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Item/components/index.ts
@@ -0,0 +1,3 @@
+export {Action} from './Action';
+export {Handle} from './Handle';
+export {Remove} from './Remove';
diff --git a/src/client/components/SortableGrid/components/Item/index.ts b/src/client/components/SortableGrid/components/Item/index.ts
new file mode 100644
index 0000000..4ec4852
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Item/index.ts
@@ -0,0 +1,2 @@
+export {Item} from './Item';
+export {Action, Handle, Remove} from './components';
diff --git a/src/client/components/SortableGrid/components/List/List.module.css b/src/client/components/SortableGrid/components/List/List.module.css
new file mode 100644
index 0000000..e56b0bd
--- /dev/null
+++ b/src/client/components/SortableGrid/components/List/List.module.css
@@ -0,0 +1,25 @@
+.List {
+ display: grid;
+ grid-auto-rows: max-content;
+ box-sizing: border-box;
+ min-width: 350px;
+ grid-gap: 10px;
+ padding: 20px;
+ padding-bottom: 0;
+ margin: 10px;
+ border-radius: 5px;
+ min-height: 200px;
+ transition: background-color 350ms ease;
+ grid-template-columns: repeat(var(--columns, 1), 1fr);
+
+ &:after {
+ content: '';
+ height: 10px;
+ grid-column-start: span var(--columns, 1);
+ }
+
+ &.horizontal {
+ width: 100%;
+ grid-auto-flow: column;
+ }
+}
diff --git a/src/client/components/SortableGrid/components/List/List.module.css.d.ts b/src/client/components/SortableGrid/components/List/List.module.css.d.ts
new file mode 100644
index 0000000..5258e0e
--- /dev/null
+++ b/src/client/components/SortableGrid/components/List/List.module.css.d.ts
@@ -0,0 +1,8 @@
+// This file is automatically generated.
+// Please do not change this file!
+interface CssExports {
+ 'List': string;
+ 'horizontal': string;
+}
+export const cssExports: CssExports;
+export default cssExports;
diff --git a/src/client/components/SortableGrid/components/List/List.tsx b/src/client/components/SortableGrid/components/List/List.tsx
new file mode 100644
index 0000000..98af87c
--- /dev/null
+++ b/src/client/components/SortableGrid/components/List/List.tsx
@@ -0,0 +1,30 @@
+import React, {forwardRef} from 'react';
+import classNames from 'classnames';
+
+import styles from './List.module.css';
+
+export interface Props {
+ children: React.ReactNode;
+ columns?: number;
+ style?: React.CSSProperties;
+ horizontal?: boolean;
+}
+
+export const List = forwardRef(
+ ({children, columns = 1, horizontal, style}: Props, ref) => {
+ return (
+
+ );
+ }
+);
diff --git a/src/client/components/SortableGrid/components/List/index.ts b/src/client/components/SortableGrid/components/List/index.ts
new file mode 100644
index 0000000..6e4d8ea
--- /dev/null
+++ b/src/client/components/SortableGrid/components/List/index.ts
@@ -0,0 +1 @@
+export {List} from './List';
diff --git a/src/client/components/SortableGrid/components/OverflowWrapper/OverflowWrapper.module.css b/src/client/components/SortableGrid/components/OverflowWrapper/OverflowWrapper.module.css
new file mode 100644
index 0000000..87f8523
--- /dev/null
+++ b/src/client/components/SortableGrid/components/OverflowWrapper/OverflowWrapper.module.css
@@ -0,0 +1,8 @@
+.OverflowWrapper {
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ right: 0;
+ overflow: hidden;
+}
diff --git a/src/client/components/SortableGrid/components/OverflowWrapper/OverflowWrapper.module.css.d.ts b/src/client/components/SortableGrid/components/OverflowWrapper/OverflowWrapper.module.css.d.ts
new file mode 100644
index 0000000..317e5f9
--- /dev/null
+++ b/src/client/components/SortableGrid/components/OverflowWrapper/OverflowWrapper.module.css.d.ts
@@ -0,0 +1,7 @@
+// This file is automatically generated.
+// Please do not change this file!
+interface CssExports {
+ 'OverflowWrapper': string;
+}
+export const cssExports: CssExports;
+export default cssExports;
diff --git a/src/client/components/SortableGrid/components/OverflowWrapper/OverflowWrapper.tsx b/src/client/components/SortableGrid/components/OverflowWrapper/OverflowWrapper.tsx
new file mode 100644
index 0000000..71ad82a
--- /dev/null
+++ b/src/client/components/SortableGrid/components/OverflowWrapper/OverflowWrapper.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+
+import styles from './OverflowWrapper.module.css';
+
+interface Props {
+ children: React.ReactNode;
+}
+
+export function OverflowWrapper({children}: Props) {
+ return {children}
;
+}
diff --git a/src/client/components/SortableGrid/components/OverflowWrapper/index.ts b/src/client/components/SortableGrid/components/OverflowWrapper/index.ts
new file mode 100644
index 0000000..4b72829
--- /dev/null
+++ b/src/client/components/SortableGrid/components/OverflowWrapper/index.ts
@@ -0,0 +1 @@
+export {OverflowWrapper} from './OverflowWrapper';
diff --git a/src/client/components/SortableGrid/components/Wrapper/Wrapper.module.css b/src/client/components/SortableGrid/components/Wrapper/Wrapper.module.css
new file mode 100644
index 0000000..517b32d
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Wrapper/Wrapper.module.css
@@ -0,0 +1,11 @@
+.Wrapper {
+ display: flex;
+ width: 100%;
+ box-sizing: border-box;
+ padding: 20px;
+ justify-content: flex-start;
+
+ &.center {
+ justify-content: center;
+ }
+}
diff --git a/src/client/components/SortableGrid/components/Wrapper/Wrapper.module.css.d.ts b/src/client/components/SortableGrid/components/Wrapper/Wrapper.module.css.d.ts
new file mode 100644
index 0000000..967f57c
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Wrapper/Wrapper.module.css.d.ts
@@ -0,0 +1,8 @@
+// This file is automatically generated.
+// Please do not change this file!
+interface CssExports {
+ 'Wrapper': string;
+ 'center': string;
+}
+export const cssExports: CssExports;
+export default cssExports;
diff --git a/src/client/components/SortableGrid/components/Wrapper/Wrapper.tsx b/src/client/components/SortableGrid/components/Wrapper/Wrapper.tsx
new file mode 100644
index 0000000..68314aa
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Wrapper/Wrapper.tsx
@@ -0,0 +1,21 @@
+import React from 'react';
+import classNames from 'classnames';
+
+import styles from './Wrapper.module.css';
+
+interface Props {
+ children: React.ReactNode;
+ center?: boolean;
+ style?: React.CSSProperties;
+}
+
+export function Wrapper({children, center, style}: Props) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/client/components/SortableGrid/components/Wrapper/index.ts b/src/client/components/SortableGrid/components/Wrapper/index.ts
new file mode 100644
index 0000000..c69727c
--- /dev/null
+++ b/src/client/components/SortableGrid/components/Wrapper/index.ts
@@ -0,0 +1 @@
+export {Wrapper} from './Wrapper';
diff --git a/src/client/components/SortableGrid/components/index.ts b/src/client/components/SortableGrid/components/index.ts
new file mode 100644
index 0000000..000736d
--- /dev/null
+++ b/src/client/components/SortableGrid/components/index.ts
@@ -0,0 +1,13 @@
+export { Button } from "./Button";
+export { ConfirmModal } from "./ConfirmModal";
+export { Container } from "./Container";
+export type { ContainerProps } from "./Container";
+export { Draggable } from "./Draggable";
+export { Droppable } from "./Droppable";
+export { Item, Action, Handle, Remove } from "./Item";
+export { FloatingControls } from "./FloatingControls";
+export { Grid } from "./Grid";
+export { GridContainer } from "./GridContainer";
+export { List } from "./List";
+export { OverflowWrapper } from "./OverflowWrapper";
+export { Wrapper } from "./Wrapper";
diff --git a/src/client/components/SortableGrid/utilities/createRange.ts b/src/client/components/SortableGrid/utilities/createRange.ts
new file mode 100644
index 0000000..158b84c
--- /dev/null
+++ b/src/client/components/SortableGrid/utilities/createRange.ts
@@ -0,0 +1,8 @@
+const defaultInitializer = (index: number) => index;
+
+export function createRange(
+ length: number,
+ initializer: (index: number) => any = defaultInitializer
+): T[] {
+ return [...new Array(length)].map((_, index) => initializer(index));
+}
diff --git a/src/client/components/SortableGrid/utilities/index.ts b/src/client/components/SortableGrid/utilities/index.ts
new file mode 100644
index 0000000..f1c4389
--- /dev/null
+++ b/src/client/components/SortableGrid/utilities/index.ts
@@ -0,0 +1 @@
+export {createRange} from './createRange';
diff --git a/tsconfig.json b/tsconfig.json
index bfbad80..3d284ff 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -10,13 +10,31 @@
"sourceMap": true,
"outDir": "./dist/",
"skipLibCheck": true,
- "lib": ["dom", "dom.iterable", "esnext", "webworker"]
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext",
+ "webworker"
+ ],
+ "plugins": [
+ {
+ "name": "typescript-plugin-css-modules"
+ }
+ ]
},
"settings": {
"eslint.workingDirectories": [
- { "directory": "./node_modules", "changeProcessCWD": true }
+ {
+ "directory": "./node_modules",
+ "changeProcessCWD": true
+ }
]
},
- "exclude": ["./src/server "],
- "include": ["./src/client/*", "./src/client/types/**/*.d.ts"]
-}
+ "exclude": [
+ "./src/server "
+ ],
+ "include": [
+ "./src/client/*",
+ "./src/client/types/**/*.d.ts"
+ ]
+}
\ No newline at end of file
diff --git a/webpack.config.js b/webpack.config.js
index 606feb6..778bac7 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -40,7 +40,17 @@ module.exports = {
},
{
test: /\.css$/,
- use: ["style-loader", "css-loader"],
+ use: [
+ { loader: "style-loader" },
+ {
+ loader: "css-loader",
+ options: {
+ modules: true,
+ importLoaders: 1,
+ sourceMap: true,
+ },
+ },
+ ],
},
{
test: /\.(scss|sass)$/,
diff --git a/yarn.lock b/yarn.lock
index a9ac6b5..4eddbe5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1035,6 +1035,45 @@
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz#9283c9ce5b289a3c4f61c12757469e59377f81f3"
integrity sha512-6nFkfkmSeV/rqSaS4oWHgmpnYw194f6hmWF5is6b0J1naJZoiD0NTc9AiUwPHvWsowkjuHErCZT1wa0jg+BLIA==
+"@dnd-kit/accessibility@^3.0.0":
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/@dnd-kit/accessibility/-/accessibility-3.0.0.tgz#b56e3750414fd907b7d6972b3116aa8f96d07fde"
+ integrity sha512-QwaQ1IJHQHMMuAGOOYHQSx7h7vMZPfO97aDts8t5N/MY7n2QTDSnW+kF7uRQ1tVBkr6vJ+BqHWG5dlgGvwVjow==
+ dependencies:
+ tslib "^2.0.0"
+
+"@dnd-kit/core@^4.0.0":
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/@dnd-kit/core/-/core-4.0.0.tgz#14e530f859d95ca49aea57878c95bc5f15cf5aac"
+ integrity sha512-ZbQxOoZs/eH8FJlQAipw+FVF6AvjoTR6aAF6JrweJ4Ic45WhuxHHRN/7u50MYhaMuvG6HF24qks0hZKQUe1EDg==
+ dependencies:
+ "@dnd-kit/accessibility" "^3.0.0"
+ "@dnd-kit/utilities" "^3.0.0"
+ tslib "^2.0.0"
+
+"@dnd-kit/modifiers@^4.0.0":
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/@dnd-kit/modifiers/-/modifiers-4.0.0.tgz#d1577b806b2319f14a1a0a155f270e672cfca636"
+ integrity sha512-4OkNTamneH9u3YMJqG6yJ6cwFoEd/4yY9BF39TgmDh9vyMK2MoPZFVAV0vOEm193ZYsPczq3Af5tJFtJhR9jJQ==
+ dependencies:
+ "@dnd-kit/utilities" "^3.0.0"
+ tslib "^2.0.0"
+
+"@dnd-kit/sortable@^5.0.0":
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/@dnd-kit/sortable/-/sortable-5.0.0.tgz#33841fcbc2f2490aaae42906b7497a2033242faa"
+ integrity sha512-CvTuaTIqzvZe8r1Lt+2tPK7VZKfE0SQkFTcAMayf8jS6MFgX6TV5GAIb1tKvbqrGXkqqTZeA2EaeMzjFyyfqkQ==
+ dependencies:
+ "@dnd-kit/utilities" "^3.0.0"
+ tslib "^2.0.0"
+
+"@dnd-kit/utilities@^3.0.0":
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/@dnd-kit/utilities/-/utilities-3.0.0.tgz#43b86e9d911b625ee54786e9daae5a521949b8a6"
+ integrity sha512-E2UG8guqa532xB8kEe2rFEI/U2W7zuxv0zYorL3knnGJxMGBoI7132i4ULmQs/aunQvNy8kj6Ts39n+APeU7BQ==
+ dependencies:
+ tslib "^2.0.0"
+
"@emotion/cache@^11.4.0":
version "11.4.0"
resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.4.0.tgz#293fc9d9a7a38b9aad8e9337e5014366c3b09ac0"
@@ -4299,6 +4338,14 @@ css-loader@^5.1.2:
schema-utils "^3.0.0"
semver "^7.3.5"
+css-modules-typescript-loader@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/css-modules-typescript-loader/-/css-modules-typescript-loader-4.0.1.tgz#0b818cf647fefd8f9fb3d4469374e69ab1e72742"
+ integrity sha512-vXrUAwPGcRaopnGdg7I5oqv/NSSKQRN5L80m3f49uSGinenU5DTNsMFHS+2roh5tXqpY5+yAAKAl7A2HDvumzg==
+ dependencies:
+ line-diff "^2.0.1"
+ loader-utils "^1.2.3"
+
css-select-base-adapter@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7"
@@ -5242,6 +5289,14 @@ eslint-module-utils@^2.6.2:
debug "^3.2.7"
pkg-dir "^2.0.0"
+eslint-plugin-css-modules@^2.11.0:
+ version "2.11.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-css-modules/-/eslint-plugin-css-modules-2.11.0.tgz#8de4d01d523a2d51c03043fa8004aab6b6cf3b1a"
+ integrity sha512-CLvQvJOMlCywZzaI4HVu7QH/ltgNXvCg7giJGiE+sA9wh5zQ+AqTgftAzrERV22wHe1p688wrU/Zwxt1Ry922w==
+ dependencies:
+ gonzales-pe "^4.0.3"
+ lodash "^4.17.2"
+
eslint-plugin-import@^2.22.1:
version "2.24.2"
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.24.2.tgz#2c8cd2e341f3885918ee27d18479910ade7bb4da"
@@ -6421,6 +6476,13 @@ globule@^1.0.0:
lodash "~4.17.10"
minimatch "~3.0.2"
+gonzales-pe@^4.0.3:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/gonzales-pe/-/gonzales-pe-4.3.0.tgz#fe9dec5f3c557eead09ff868c65826be54d067b3"
+ integrity sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==
+ dependencies:
+ minimist "^1.2.5"
+
got@^6.7.1:
version "6.7.1"
resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0"
@@ -8363,6 +8425,11 @@ lcid@^2.0.0:
dependencies:
invert-kv "^2.0.0"
+levdist@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/levdist/-/levdist-1.0.0.tgz#91d7a3044964f2ccc421a0477cac827fe75c5718"
+ integrity sha1-kdejBElk8szEIaBHfKyCf+dcVxg=
+
leven@2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580"
@@ -8499,6 +8566,13 @@ libnpmversion@^1.2.1:
semver "^7.3.5"
stringify-package "^1.0.1"
+line-diff@^2.0.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/line-diff/-/line-diff-2.1.1.tgz#a389799b931375a3b1e764964ad0b0b3ce60d6f6"
+ integrity sha512-vswdynAI5AMPJacOo2o+JJ4caDJbnY2NEqms4MhMW0NJbjh3skP/brpVTAgBxrg55NRZ2Vtw88ef18hnagIpYQ==
+ dependencies:
+ levdist "^1.0.0"
+
lines-and-columns@^1.1.6:
version "1.1.6"
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
@@ -8542,7 +8616,7 @@ loader-runner@^4.2.0:
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384"
integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==
-loader-utils@^1.0.0, loader-utils@^1.1.0, loader-utils@^1.4.0:
+loader-utils@^1.0.0, loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613"
integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==
@@ -8625,7 +8699,7 @@ lodash.truncate@^4.4.2:
resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=
-lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.7.0, lodash@~4.17.10:
+lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.2, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.7.0, lodash@~4.17.10:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -13370,7 +13444,7 @@ tslib@^1.13.0, tslib@^1.8.1, tslib@^1.9.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
-tslib@^2.0.1, tslib@^2.0.3, tslib@^2.3.0, tslib@^2.3.1:
+tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.3.0, tslib@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==