1
0
Fork 0
mirror of https://git.sr.ht/~ashkeel/strimertul synced 2024-09-20 02:00:49 +00:00

Commands are finally done!

This commit is contained in:
Ash Keel 2022-01-07 09:41:53 +01:00
parent ea87bfbaf3
commit 1ae499a664
No known key found for this signature in database
GPG key ID: BAD8D93E7314ED3E
15 changed files with 1060 additions and 20 deletions

View file

@ -394,6 +394,11 @@
} }
} }
}, },
"@fontsource/space-mono": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/@fontsource/space-mono/-/space-mono-4.5.0.tgz",
"integrity": "sha512-1tzGlLH2N/k1sovGSA4s7MpIRMciKeJbXKfdrKc1H84lg1FPcmiYGYdWiQA5MoQyjF6Bqx+IWpyIcOktMKvViA=="
},
"@nodelib/fs.scandir": { "@nodelib/fs.scandir": {
"version": "2.1.4", "version": "2.1.4",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz",
@ -433,6 +438,31 @@
"@babel/runtime": "^7.13.10" "@babel/runtime": "^7.13.10"
} }
}, },
"@radix-ui/react-alert-dialog": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-0.1.5.tgz",
"integrity": "sha512-Lq9h3GSvw752e7dFll3UWvm4uWiTlYAXLFX6wr/VQPRoa7XaQO8/1NBu4ikLHAecGEd/uDGZLY3aP7ovGPQYtg==",
"requires": {
"@babel/runtime": "^7.13.10",
"@radix-ui/primitive": "0.1.0",
"@radix-ui/react-compose-refs": "0.1.0",
"@radix-ui/react-context": "0.1.1",
"@radix-ui/react-dialog": "0.1.5",
"@radix-ui/react-primitive": "0.1.3",
"@radix-ui/react-slot": "0.1.2"
},
"dependencies": {
"@radix-ui/react-primitive": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-0.1.3.tgz",
"integrity": "sha512-fcyADaaAx2jdqEDLsTs6aX50S3L1c9K9CC6XMpJpuXFJCU4n9PGTFDZRtY2gAoXXoRCPIBsklCopSmGb6SsDjQ==",
"requires": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-slot": "0.1.2"
}
}
}
},
"@radix-ui/react-checkbox": { "@radix-ui/react-checkbox": {
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-0.1.4.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-0.1.4.tgz",
@ -521,6 +551,103 @@
"@babel/runtime": "^7.13.10" "@babel/runtime": "^7.13.10"
} }
}, },
"@radix-ui/react-dialog": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-0.1.5.tgz",
"integrity": "sha512-WftvXcQSszUphCTLQkkpBIkrYYU0IYqgIvACLQady4BN4YHDgdNlrwdg2ti9QrXgq1PZ+0S/6BIaA1dmSuRQ2g==",
"requires": {
"@babel/runtime": "^7.13.10",
"@radix-ui/primitive": "0.1.0",
"@radix-ui/react-compose-refs": "0.1.0",
"@radix-ui/react-context": "0.1.1",
"@radix-ui/react-dismissable-layer": "0.1.3",
"@radix-ui/react-focus-guards": "0.1.0",
"@radix-ui/react-focus-scope": "0.1.3",
"@radix-ui/react-id": "0.1.4",
"@radix-ui/react-portal": "0.1.3",
"@radix-ui/react-presence": "0.1.1",
"@radix-ui/react-primitive": "0.1.3",
"@radix-ui/react-slot": "0.1.2",
"@radix-ui/react-use-controllable-state": "0.1.0",
"aria-hidden": "^1.1.1",
"react-remove-scroll": "^2.4.0"
},
"dependencies": {
"@radix-ui/react-id": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-0.1.4.tgz",
"integrity": "sha512-/hq5m/D0ZfJWOS7TLF+G0l08KDRs87LBE46JkAvgKkg1fW4jkucx9At9D9vauIPSbdNmww5kXEp566hMlA8eXA==",
"requires": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-use-layout-effect": "0.1.0"
}
},
"@radix-ui/react-primitive": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-0.1.3.tgz",
"integrity": "sha512-fcyADaaAx2jdqEDLsTs6aX50S3L1c9K9CC6XMpJpuXFJCU4n9PGTFDZRtY2gAoXXoRCPIBsklCopSmGb6SsDjQ==",
"requires": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-slot": "0.1.2"
}
}
}
},
"@radix-ui/react-dismissable-layer": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-0.1.3.tgz",
"integrity": "sha512-3veE7M8K13Qb+6+tC3DHWmWV9VMuuRoZvRLdrvz7biSraK/qkGBN4LbKZDaTdw2D2HS7RNpSd/sF8pFd3TaAgA==",
"requires": {
"@babel/runtime": "^7.13.10",
"@radix-ui/primitive": "0.1.0",
"@radix-ui/react-context": "0.1.1",
"@radix-ui/react-primitive": "0.1.3",
"@radix-ui/react-use-body-pointer-events": "0.1.0",
"@radix-ui/react-use-callback-ref": "0.1.0",
"@radix-ui/react-use-escape-keydown": "0.1.0"
},
"dependencies": {
"@radix-ui/react-primitive": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-0.1.3.tgz",
"integrity": "sha512-fcyADaaAx2jdqEDLsTs6aX50S3L1c9K9CC6XMpJpuXFJCU4n9PGTFDZRtY2gAoXXoRCPIBsklCopSmGb6SsDjQ==",
"requires": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-slot": "0.1.2"
}
}
}
},
"@radix-ui/react-focus-guards": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-0.1.0.tgz",
"integrity": "sha512-kRx/swAjEfBpQ3ns7J3H4uxpXuWCqN7MpALiSDOXiyo2vkWv0L9sxvbpZeTulINuE3CGMzicVMuNc/VWXjFKOg==",
"requires": {
"@babel/runtime": "^7.13.10"
}
},
"@radix-ui/react-focus-scope": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-0.1.3.tgz",
"integrity": "sha512-bKi+lw14SriQqYWMBe13b/wvxSqYMC+3FylMUEwOKA6JrBoldpkhX5XffGDdpDRTTpjbncdH3H7d1PL5Bs7Ikg==",
"requires": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-compose-refs": "0.1.0",
"@radix-ui/react-primitive": "0.1.3",
"@radix-ui/react-use-callback-ref": "0.1.0"
},
"dependencies": {
"@radix-ui/react-primitive": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-0.1.3.tgz",
"integrity": "sha512-fcyADaaAx2jdqEDLsTs6aX50S3L1c9K9CC6XMpJpuXFJCU4n9PGTFDZRtY2gAoXXoRCPIBsklCopSmGb6SsDjQ==",
"requires": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-slot": "0.1.2"
}
}
}
},
"@radix-ui/react-icons": { "@radix-ui/react-icons": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.0.3.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.0.3.tgz",
@ -547,6 +674,27 @@
"@radix-ui/react-primitive": "0.1.2" "@radix-ui/react-primitive": "0.1.2"
} }
}, },
"@radix-ui/react-portal": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-0.1.3.tgz",
"integrity": "sha512-DrV+sPYLs0HhmX5/b7yRT6nLM9Nl6FtQe2KUG+46kiCOKQ+0XzNMO5hmeQtyq0mRf/qlC02rFu6OMsWpIqVsJg==",
"requires": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-primitive": "0.1.3",
"@radix-ui/react-use-layout-effect": "0.1.0"
},
"dependencies": {
"@radix-ui/react-primitive": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-0.1.3.tgz",
"integrity": "sha512-fcyADaaAx2jdqEDLsTs6aX50S3L1c9K9CC6XMpJpuXFJCU4n9PGTFDZRtY2gAoXXoRCPIBsklCopSmGb6SsDjQ==",
"requires": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-slot": "0.1.2"
}
}
}
},
"@radix-ui/react-presence": { "@radix-ui/react-presence": {
"version": "0.1.1", "version": "0.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-0.1.1.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-0.1.1.tgz",
@ -646,6 +794,15 @@
} }
} }
}, },
"@radix-ui/react-use-body-pointer-events": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-body-pointer-events/-/react-use-body-pointer-events-0.1.0.tgz",
"integrity": "sha512-svPyoHCcwOq/vpWNEvdH/yD91vN9p8BtiozNQbjVmJRxQ/vS12zqk70AxTGWe+2ZKHq2sggpEQNTv1JHyVFlnQ==",
"requires": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-use-layout-effect": "0.1.0"
}
},
"@radix-ui/react-use-callback-ref": { "@radix-ui/react-use-callback-ref": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-0.1.0.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-0.1.0.tgz",
@ -663,6 +820,15 @@
"@radix-ui/react-use-callback-ref": "0.1.0" "@radix-ui/react-use-callback-ref": "0.1.0"
} }
}, },
"@radix-ui/react-use-escape-keydown": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-0.1.0.tgz",
"integrity": "sha512-tDLZbTGFmvXaazUXXv8kYbiCcbAE8yKgng9s95d8fCO+Eundv0Jngbn/hKPhDDs4jj9ChwRX5cDDnlaN+ugYYQ==",
"requires": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-use-callback-ref": "0.1.0"
}
},
"@radix-ui/react-use-layout-effect": { "@radix-ui/react-use-layout-effect": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-0.1.0.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-0.1.0.tgz",
@ -952,6 +1118,14 @@
"sprintf-js": "~1.0.2" "sprintf-js": "~1.0.2"
} }
}, },
"aria-hidden": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.1.3.tgz",
"integrity": "sha512-RhVWFtKH5BiGMycI72q2RAFMLQi8JP9bLuQXgR5a8Znp7P5KOIADSJeyfI8PCVxLEp067B2HbP5JIiI/PXIZeA==",
"requires": {
"tslib": "^1.0.0"
}
},
"array-includes": { "array-includes": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.3.tgz", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.3.tgz",
@ -1206,6 +1380,11 @@
"object-keys": "^1.0.12" "object-keys": "^1.0.12"
} }
}, },
"detect-node-es": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="
},
"dir-glob": { "dir-glob": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@ -1859,6 +2038,11 @@
"has-symbols": "^1.0.1" "has-symbols": "^1.0.1"
} }
}, },
"get-nonce": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
"integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="
},
"get-symbol-description": { "get-symbol-description": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz",
@ -2059,6 +2243,14 @@
"side-channel": "^1.0.4" "side-channel": "^1.0.4"
} }
}, },
"invariant": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
"requires": {
"loose-envify": "^1.0.0"
}
},
"is-arrayish": { "is-arrayish": {
"version": "0.2.1", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
@ -2712,6 +2904,27 @@
"react-is": "^16.13.1" "react-is": "^16.13.1"
} }
}, },
"react-remove-scroll": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.4.3.tgz",
"integrity": "sha512-lGWYXfV6jykJwbFpsuPdexKKzp96f3RbvGapDSIdcyGvHb7/eqyn46C7/6h+rUzYar1j5mdU+XECITHXCKBk9Q==",
"requires": {
"react-remove-scroll-bar": "^2.1.0",
"react-style-singleton": "^2.1.0",
"tslib": "^1.0.0",
"use-callback-ref": "^1.2.3",
"use-sidecar": "^1.0.1"
}
},
"react-remove-scroll-bar": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.2.0.tgz",
"integrity": "sha512-UU9ZBP1wdMR8qoUs7owiVcpaPwsQxUDC2lypP6mmixaGlARZa7ZIBx1jcuObLdhMOvCsnZcvetOho0wzPa9PYg==",
"requires": {
"react-style-singleton": "^2.1.0",
"tslib": "^1.0.0"
}
},
"react-router": { "react-router": {
"version": "6.1.1", "version": "6.1.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.1.1.tgz", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.1.1.tgz",
@ -2729,6 +2942,16 @@
"react-router": "6.1.1" "react-router": "6.1.1"
} }
}, },
"react-style-singleton": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.1.1.tgz",
"integrity": "sha512-jNRp07Jza6CBqdRKNgGhT3u9umWvils1xsuMOjZlghBDH2MU0PL2WZor4PGYjXpnRCa9DQSlHMs/xnABWOwYbA==",
"requires": {
"get-nonce": "^1.0.0",
"invariant": "^2.2.4",
"tslib": "^1.0.0"
}
},
"react-toastify": { "react-toastify": {
"version": "8.1.0", "version": "8.1.0",
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-8.1.0.tgz", "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-8.1.0.tgz",
@ -3143,8 +3366,7 @@
"tslib": { "tslib": {
"version": "1.14.1", "version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
"dev": true
}, },
"tsutils": { "tsutils": {
"version": "3.21.0", "version": "3.21.0",
@ -3196,6 +3418,20 @@
"punycode": "^2.1.0" "punycode": "^2.1.0"
} }
}, },
"use-callback-ref": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.2.5.tgz",
"integrity": "sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg=="
},
"use-sidecar": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.0.5.tgz",
"integrity": "sha512-k9jnrjYNwN6xYLj1iaGhonDghfvmeTmYjAiGvOr7clwKfPjMXJf4/HOr7oT5tJwYafgp2tG2l3eZEOfoELiMcA==",
"requires": {
"detect-node-es": "^1.1.0",
"tslib": "^1.9.3"
}
},
"v8-compile-cache": { "v8-compile-cache": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",

View file

@ -3,8 +3,11 @@
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"@billjs/event-emitter": "^1.0.3", "@billjs/event-emitter": "^1.0.3",
"@fontsource/space-mono": "^4.5.0",
"@radix-ui/colors": "^0.1.8", "@radix-ui/colors": "^0.1.8",
"@radix-ui/react-alert-dialog": "^0.1.5",
"@radix-ui/react-checkbox": "^0.1.4", "@radix-ui/react-checkbox": "^0.1.4",
"@radix-ui/react-dialog": "^0.1.5",
"@radix-ui/react-icons": "^1.0.3", "@radix-ui/react-icons": "^1.0.3",
"@radix-ui/react-label": "^0.1.3", "@radix-ui/react-label": "^0.1.3",
"@radix-ui/react-tabs": "^0.1.4", "@radix-ui/react-tabs": "^0.1.4",

View file

@ -4,6 +4,7 @@ import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom'; import { BrowserRouter } from 'react-router-dom';
import 'inter-ui/inter.css'; import 'inter-ui/inter.css';
import '@fontsource/space-mono/index.css';
import 'normalize.css/normalize.css'; import 'normalize.css/normalize.css';
import 'react-toastify/dist/ReactToastify.css'; import 'react-toastify/dist/ReactToastify.css';
import 'overlayscrollbars/css/OverlayScrollbars.css'; import 'overlayscrollbars/css/OverlayScrollbars.css';

View file

@ -104,14 +104,40 @@
"title": "Bot commands", "title": "Bot commands",
"desc": "Define custom chat commands to set up autoresponders, counters, etc.", "desc": "Define custom chat commands to set up autoresponders, counters, etc.",
"add-button": "New command", "add-button": "New command",
"search-placeholder": "Search command by name" "search-placeholder": "Search command by name",
"command-header-new": "New command",
"command-header-edit": "Edit command",
"command-name": "Command name",
"command-name-placeholder": "!command",
"command-desc": "Description (optional)",
"command-desc-placeholder": "This command does something",
"command-response": "Response",
"command-response-placeholder": "Hello {0}!",
"command-acl": "Access level",
"command-acl-help": "This specifies the minimum level, eg. if you choose VIPs, moderators and streamer can still use the command",
"command-action-new": "Create",
"command-action-edit": "Edit",
"acl": {
"everyone": "Everyone",
"subscribers": "Subscribers",
"vip": "VIPs",
"moderators": "Moderators",
"streamer": "Streamer only"
},
"remove-command-title": "Remove command {{name}}?"
} }
}, },
"form-actions": { "form-actions": {
"save": "Save", "save": "Save",
"saving": "Saving...", "saving": "Saving...",
"saved": "Saved", "saved": "Saved",
"error": "Error" "error": "Error",
"edit": "Edit",
"enable": "Enable",
"disable": "Disable",
"delete": "Delete",
"cancel": "Cancel",
"ok": "OK"
}, },
"debug": { "debug": {
"dev-build": "Development build" "dev-build": "Development build"

View file

@ -25,12 +25,15 @@ interface TwitchBotConfig {
chat_history: number; chat_history: number;
} }
export type AccessLevelType = export const accessLevels = [
| 'everyone' 'everyone',
| 'subscribers' 'subscribers',
| 'vip' 'vip',
| 'moderators' 'moderators',
| 'streamer'; 'streamer',
] as const;
export type AccessLevelType = typeof accessLevels[number];
export interface TwitchBotCustomCommand { export interface TwitchBotCustomCommand {
description: string; description: string;

View file

@ -0,0 +1,74 @@
import React from 'react';
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';
import { VariantProps } from '@stitches/react';
import { useTranslation } from 'react-i18next';
import {
AlertOverlay,
AlertContainer,
AlertTitle,
AlertDescription,
AlertActions,
AlertAction,
AlertCancel,
} from '../theme/alert';
import { Button } from '../theme';
export interface DialogProps {
title?: string;
description?: string;
actionText?: string;
showCancel?: boolean;
cancelText?: string;
actionButtonProps?: VariantProps<typeof Button>;
variation?: 'default' | 'danger';
onAction?: () => void;
}
function DialogContent({
title,
description,
children,
actionText,
actionButtonProps,
showCancel,
cancelText,
variation,
onAction,
}: React.PropsWithChildren<DialogProps>) {
const { t } = useTranslation();
return (
<AlertDialogPrimitive.Portal>
<AlertOverlay />
<AlertContainer variation={variation ?? 'default'}>
{title && (
<AlertTitle variation={variation ?? 'default'}>{title}</AlertTitle>
)}
{description && (
<AlertDescription variation={variation ?? 'default'}>
{description}
</AlertDescription>
)}
{children}
<AlertActions>
<AlertAction asChild>
<Button
variation="primary"
{...actionButtonProps}
onClick={() => (onAction ? onAction() : null)}
>
{actionText || t('form-actions.ok')}
</Button>
</AlertAction>
{showCancel && (
<AlertCancel asChild>
<Button>{cancelText || t('form-actions.cancel')}</Button>
</AlertCancel>
)}
</AlertActions>
</AlertContainer>
</AlertDialogPrimitive.Portal>
);
}
export default React.memo(DialogContent);

View file

@ -0,0 +1,41 @@
import React from 'react';
import * as DialogPrimitive from '@radix-ui/react-dialog';
import { Cross2Icon } from '@radix-ui/react-icons';
import {
DialogOverlay,
DialogContainer,
IconButton,
DialogTitle,
DialogDescription,
} from '../theme';
export interface DialogProps {
title?: string;
description?: string;
closeButton?: boolean;
}
function DialogContent({
title,
description,
children,
}: React.PropsWithChildren<DialogProps>) {
return (
<DialogPrimitive.Portal>
<DialogOverlay />
<DialogContainer>
{title && <DialogTitle>{title}</DialogTitle>}
{description && <DialogDescription>{description}</DialogDescription>}
{children}
<DialogPrimitive.DialogClose asChild>
<IconButton>
<Cross2Icon />
</IconButton>
</DialogPrimitive.DialogClose>
</DialogContainer>
</DialogPrimitive.Portal>
);
}
export default React.memo(DialogContent);

View file

@ -27,7 +27,7 @@ function SaveButton(
</Button> </Button>
); );
default: default:
return <Button>{t('form-actions.save')}</Button>; return <Button variation="primary">{t('form-actions.save')}</Button>;
} }
} }

View file

@ -183,7 +183,7 @@ function WebhookIntegration() {
return ( return (
<> <>
<p>{t('pages.stulbe.auth-message')}</p> <p>{t('pages.stulbe.auth-message')}</p>
<Button onClick={startAuthFlow} disabled={!client}> <Button variation="primary" onClick={startAuthFlow} disabled={!client}>
<ExternalLinkIcon /> {t('pages.stulbe.auth-button')} <ExternalLinkIcon /> {t('pages.stulbe.auth-button')}
</Button> </Button>
<SectionHeader>{t('pages.stulbe.current-status')}</SectionHeader> <SectionHeader>{t('pages.stulbe.current-status')}</SectionHeader>
@ -191,7 +191,7 @@ function WebhookIntegration() {
<SectionHeader>{t('pages.stulbe.sim-events')}</SectionHeader> <SectionHeader>{t('pages.stulbe.sim-events')}</SectionHeader>
<ButtonGroup> <ButtonGroup>
{Object.keys(eventSubTestFn).map((ev: keyof typeof eventsubTests) => ( {Object.keys(eventSubTestFn).map((ev: keyof typeof eventsubTests) => (
<Button onClick={() => sendFakeEvent(ev)}> <Button key={ev} onClick={() => sendFakeEvent(ev)}>
{t(`pages.stulbe.sim.${ev}`, { defaultValue: ev })} {t(`pages.stulbe.sim.${ev}`, { defaultValue: ev })}
</Button> </Button>
))} ))}

View file

@ -1,18 +1,330 @@
import { PlusIcon } from '@radix-ui/react-icons'; import { PlusIcon } from '@radix-ui/react-icons';
import React from 'react'; import React, { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { useModule } from '../../lib/react-utils';
import { modules } from '../../store/api/reducer';
import {
accessLevels,
AccessLevelType,
TwitchBotCustomCommand,
} from '../../store/api/types';
import AlertContent from '../components/AlertContent';
import DialogContent from '../components/DialogContent';
import { import {
Button, Button,
ComboBox,
Dialog,
DialogActions,
DialogClose,
Field,
FieldNote,
FlexRow, FlexRow,
InputBox, InputBox,
Label,
PageContainer, PageContainer,
PageHeader, PageHeader,
PageTitle, PageTitle,
styled,
Textarea,
TextBlock, TextBlock,
} from '../theme'; } from '../theme';
import { Alert, AlertTrigger } from '../theme/alert';
const CommandList = styled('div', { marginTop: '1rem' });
const CommandItemContainer = styled('article', {
backgroundColor: '$gray2',
margin: '0.5rem 0',
padding: '0.5rem',
borderLeft: '5px solid $teal8',
borderRadius: '0.25rem',
borderBottom: '1px solid $gray4',
transition: 'all 50ms',
'&:hover': {
backgroundColor: '$gray3',
},
variants: {
status: {
enabled: {},
disabled: {
borderLeftColor: '$red7',
backgroundColor: '$gray3',
color: '$gray10',
},
},
},
});
const CommandHeader = styled('header', {
display: 'flex',
gap: '0.5rem',
alignItems: 'center',
marginBottom: '0.4rem',
});
const CommandName = styled('span', {
color: '$teal10',
fontWeight: 'bold',
variants: {
status: {
enabled: {},
disabled: {
color: '$gray10',
},
},
},
});
const CommandDescription = styled('span', {
flex: 1,
});
const CommandActions = styled('div', {
display: 'flex',
alignItems: 'center',
gap: '0.25rem',
});
const CommandText = styled('div', {
fontFamily: 'Space Mono',
fontSize: '10pt',
margin: '-0.5rem',
marginTop: '0',
padding: '0.5rem',
backgroundColor: '$gray4',
lineHeight: '1.2rem',
});
const ACLIndicator = styled('span', {
fontFamily: 'Space Mono',
fontSize: '10pt',
marginRight: '0.5rem',
});
interface CommandItemProps {
name: string;
item: TwitchBotCustomCommand;
onToggle?: () => void;
onEdit?: () => void;
onDelete?: () => void;
}
function CommandItemEl({
name,
item,
onToggle,
onEdit,
onDelete,
}: CommandItemProps): React.ReactElement {
const { t } = useTranslation();
return (
<CommandItemContainer status={item.enabled ? 'enabled' : 'disabled'}>
<CommandHeader>
<CommandName status={item.enabled ? 'enabled' : 'disabled'}>
{name}
</CommandName>
<CommandDescription>{item.description}</CommandDescription>
<CommandActions>
{item.access_level !== 'everyone' && (
<ACLIndicator>
{t(`pages.botcommands.acl.${item.access_level}`)}
{item.access_level !== 'streamer' && '+'}
</ACLIndicator>
)}
<Button
styling="link"
size="small"
onClick={() => (onToggle ? onToggle() : null)}
>
{t(item.enabled ? 'form-actions.disable' : 'form-actions.enable')}
</Button>
<Button
styling="link"
size="small"
onClick={() => (onEdit ? onEdit() : null)}
>
{t('form-actions.edit')}
</Button>
<Alert>
<AlertTrigger asChild>
<Button styling="link" size="small">
{t('form-actions.delete')}
</Button>
</AlertTrigger>
<AlertContent
variation="danger"
title={t('pages.botcommands.remove-command-title', { name })}
description="This cannot be undone"
actionText="Delete"
actionButtonProps={{ variation: 'danger' }}
showCancel={true}
onAction={() => (onDelete ? onDelete() : null)}
/>
</Alert>
</CommandActions>
</CommandHeader>
<CommandText>{item.response}</CommandText>
</CommandItemContainer>
);
}
const CommandItem = React.memo(CommandItemEl);
type DialogPrompt =
| { kind: 'new' }
| { kind: 'edit'; name: string; item: TwitchBotCustomCommand };
function CommandDialog({
kind,
name,
item,
onSubmit,
}: {
kind: 'new' | 'edit';
name?: string;
item?: TwitchBotCustomCommand;
onSubmit?: (name: string, item: TwitchBotCustomCommand) => void;
}) {
const [commandName, setCommandName] = useState(name ?? '');
const [description, setDescription] = useState(item?.description ?? '');
const [response, setResponse] = useState(item?.response ?? '');
const [accessLevel, setAccessLevel] = useState(
item?.access_level ?? 'everyone',
);
const { t } = useTranslation();
return (
<DialogContent title={t(`pages.botcommands.command-header-${kind}`)}>
<form
onSubmit={(e) => {
e.preventDefault();
if (onSubmit) {
onSubmit(commandName, {
...item,
description,
response,
access_level: accessLevel as AccessLevelType,
});
}
}}
>
<Field spacing="narrow" size="fullWidth">
<Label htmlFor="command-name">
{t('pages.botcommands.command-name')}
</Label>
<InputBox
id="command-name"
value={commandName}
onChange={(e) => setCommandName(e.target.value)}
placeholder={t('pages.botcommands.command-name-placeholder')}
/>
</Field>
<Field spacing="narrow" size="fullWidth">
<Label htmlFor="command-description">
{t('pages.botcommands.command-desc')}
</Label>
<InputBox
id="command-description"
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder={t('pages.botcommands.command-desc-placeholder')}
/>
</Field>
<Field spacing="narrow" size="fullWidth">
<Label htmlFor="command-response">
{t('pages.botcommands.command-response')}
</Label>
<Textarea
value={response}
onChange={(e) => setResponse(e.target.value)}
id="command-response"
placeholder={t('pages.botcommands.command-response-placeholder')}
>
{item?.response}
</Textarea>
</Field>
<Field spacing="narrow" size="fullWidth">
<Label htmlFor="command-acl">
{t('pages.botcommands.command-acl')}
</Label>
<ComboBox
id="command-acl"
value={accessLevel}
onChange={(e) => setAccessLevel(e.target.value as AccessLevelType)}
>
{accessLevels.map((level) => (
<option key={level} value={level}>
{t(`pages.botcommands.acl.${level}`)}
</option>
))}
</ComboBox>
<FieldNote>{t('pages.botcommands.command-acl-help')}</FieldNote>
</Field>
<DialogActions>
<Button variation="primary">
{t(`pages.botcommands.command-action-${kind}`)}
</Button>
<DialogClose asChild>
<Button type="button">{t('form-actions.cancel')}</Button>
</DialogClose>
</DialogActions>
</form>
</DialogContent>
);
}
export default function TwitchBotCommandsPage(): React.ReactElement { export default function TwitchBotCommandsPage(): React.ReactElement {
const [botCommands, setBotCommands] = useModule(modules.twitchBotCommands);
const [commandFilter, setCommandFilter] = useState('');
const [activeDialog, setActiveDialog] = useState<DialogPrompt>(null);
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useDispatch();
const commandFilterLC = commandFilter.toLowerCase();
const setCommand = (newName: string, data: TwitchBotCustomCommand): void => {
switch (activeDialog.kind) {
case 'new':
dispatch(
setBotCommands({
...botCommands,
[newName]: {
...data,
enabled: true,
},
}),
);
break;
case 'edit': {
const oldName = activeDialog.name;
dispatch(
setBotCommands({
...botCommands,
[oldName]: undefined,
[newName]: data,
}),
);
break;
}
}
setActiveDialog(null);
};
const deleteCommand = (cmd: string): void => {
dispatch(
setBotCommands({
...botCommands,
[cmd]: undefined,
}),
);
};
const toggleCommand = (cmd: string): void => {
dispatch(
setBotCommands({
...botCommands,
[cmd]: {
...botCommands[cmd],
enabled: !botCommands[cmd].enabled,
},
}),
);
};
return ( return (
<PageContainer> <PageContainer>
@ -22,14 +334,58 @@ export default function TwitchBotCommandsPage(): React.ReactElement {
</PageHeader> </PageHeader>
<FlexRow spacing="1" align="left"> <FlexRow spacing="1" align="left">
<Button> <Button
variation="primary"
onClick={() => setActiveDialog({ kind: 'new' })}
>
<PlusIcon /> {t('pages.botcommands.add-button')} <PlusIcon /> {t('pages.botcommands.add-button')}
</Button> </Button>
<InputBox <InputBox
css={{ flex: 1 }} css={{ flex: 1 }}
placeholder={t('pages.botcommands.search-placeholder')} placeholder={t('pages.botcommands.search-placeholder')}
value={commandFilter}
onChange={(e) => setCommandFilter(e.target.value)}
/> />
</FlexRow> </FlexRow>
<CommandList>
{Object.keys(botCommands ?? {})
?.filter((cmd) => cmd.toLowerCase().includes(commandFilterLC))
.sort()
.map((cmd) => (
<CommandItem
key={cmd}
name={cmd}
item={botCommands[cmd]}
onToggle={() => toggleCommand(cmd)}
onEdit={() =>
setActiveDialog({
kind: 'edit',
name: cmd,
item: botCommands[cmd],
})
}
onDelete={() => deleteCommand(cmd)}
/>
))}
</CommandList>
<Dialog
open={!!activeDialog}
onOpenChange={(open) => {
if (!open) {
// Reset dialog status on dialog close
setActiveDialog(null);
}
}}
>
{activeDialog && (
<CommandDialog
{...activeDialog}
onSubmit={(name, data) => setCommand(name, data)}
/>
)}
</Dialog>
</PageContainer> </PageContainer>
); );
} }

View file

@ -0,0 +1,118 @@
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';
import { keyframes } from '@stitches/react';
import { styled } from './theme';
export const Alert = AlertDialogPrimitive.Root;
export const AlertTrigger = AlertDialogPrimitive.Trigger;
export const AlertAction = AlertDialogPrimitive.AlertDialogAction;
export const AlertCancel = AlertDialogPrimitive.AlertDialogCancel;
const overlayShow = keyframes({
'0%': { opacity: 0 },
'100%': { opacity: 1 },
});
const contentShow = keyframes({
'0%': { opacity: 0, transform: 'translate(-50%, -48%) scale(.96)' },
'100%': { opacity: 1, transform: 'translate(-50%, -50%) scale(1)' },
});
export const AlertOverlay = styled(AlertDialogPrimitive.Overlay, {
backgroundColor: '$blackA10',
position: 'fixed',
inset: 0,
'@media (prefers-reduced-motion: no-preference)': {
animation: `${overlayShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`,
},
});
export const AlertContainer = styled(AlertDialogPrimitive.Content, {
backgroundColor: '$gray2',
borderRadius: '0.25rem',
boxShadow:
'hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px',
position: 'fixed',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: '90vw',
maxWidth: '600px',
maxHeight: '85vh',
padding: '1rem',
'@media (prefers-reduced-motion: no-preference)': {
animation: `${contentShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`,
},
border: '2px solid $teal8',
'&:focus': { outline: 'none' },
variants: {
variation: {
default: {},
danger: {
borderColor: '$red8',
},
},
},
});
export const AlertTitle = styled(AlertDialogPrimitive.Title, {
fontWeight: 'bold',
color: '$teal12',
fontSize: '15pt',
borderBottom: '1px solid $teal6',
margin: '-1rem',
marginBottom: '1.5rem',
padding: '1rem',
lineHeight: '1.25',
variants: {
variation: {
default: {},
danger: {
borderBottomColor: '$red6',
},
},
},
});
export const AlertDescription = styled(AlertDialogPrimitive.Description, {
margin: '10px 0 20px',
color: '$teal12',
fontSize: 15,
lineHeight: 1.5,
variants: {
variation: {
default: {},
danger: {
borderBottomColor: '$red12',
},
},
},
});
export const AlertActions = styled('div', {
display: 'flex',
gap: '0.5rem',
justifyContent: 'flex-end',
borderTop: '1px solid $gray6',
margin: '-1rem',
marginTop: '1.5rem',
padding: '1rem 1.5rem',
});
export const IconButton = styled('button', {
all: 'unset',
fontFamily: 'inherit',
borderRadius: '100%',
height: 25,
width: 25,
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
color: '$teal11',
position: 'absolute',
cursor: 'pointer',
top: 15,
right: 15,
'&:hover': { backgroundColor: '$teal4' },
'&:focus': { boxShadow: `0 0 0 2px $teal7` },
});

View file

@ -0,0 +1,92 @@
import * as DialogPrimitive from '@radix-ui/react-dialog';
import { keyframes } from '@stitches/react';
import { styled } from './theme';
export const Dialog = DialogPrimitive.Root;
export const DialogTrigger = DialogPrimitive.Trigger;
export const DialogClose = DialogPrimitive.Close;
const overlayShow = keyframes({
'0%': { opacity: 0 },
'100%': { opacity: 1 },
});
const contentShow = keyframes({
'0%': { opacity: 0, transform: 'translate(-50%, -48%) scale(.96)' },
'100%': { opacity: 1, transform: 'translate(-50%, -50%) scale(1)' },
});
export const DialogOverlay = styled(DialogPrimitive.Overlay, {
backgroundColor: '$blackA10',
position: 'fixed',
inset: 0,
'@media (prefers-reduced-motion: no-preference)': {
animation: `${overlayShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`,
},
});
export const DialogContainer = styled(DialogPrimitive.Content, {
backgroundColor: '$gray2',
borderRadius: '0.25rem',
boxShadow:
'hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px',
position: 'fixed',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: '90vw',
maxWidth: '600px',
maxHeight: '85vh',
padding: '1rem',
'@media (prefers-reduced-motion: no-preference)': {
animation: `${contentShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`,
},
'&:focus': { outline: 'none' },
});
export const DialogTitle = styled(DialogPrimitive.Title, {
fontWeight: 'bold',
color: '$teal12',
fontSize: '15pt',
borderBottom: '1px solid $teal6',
margin: '-1rem',
marginBottom: '1.5rem',
padding: '1rem',
lineHeight: '1.25',
});
export const DialogDescription = styled(DialogPrimitive.Description, {
margin: '10px 0 20px',
color: '$teal11',
fontSize: 15,
lineHeight: 1.5,
});
export const DialogActions = styled('div', {
display: 'flex',
gap: '0.5rem',
justifyContent: 'flex-end',
borderTop: '1px solid $gray6',
margin: '-1rem',
marginTop: '1.5rem',
padding: '1rem 1.5rem',
});
export const IconButton = styled('button', {
all: 'unset',
fontFamily: 'inherit',
borderRadius: '100%',
height: 25,
width: 25,
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
color: '$teal11',
position: 'absolute',
cursor: 'pointer',
top: 15,
right: 15,
'&:hover': { backgroundColor: '$teal4' },
'&:focus': { boxShadow: `0 0 0 2px $teal7` },
});

View file

@ -10,6 +10,11 @@ export const Field = styled('fieldset', {
alignItems: 'center', alignItems: 'center',
gap: '0.5rem', gap: '0.5rem',
variants: { variants: {
spacing: {
narrow: {
marginBottom: '1rem',
},
},
size: { size: {
fullWidth: { fullWidth: {
flexDirection: 'column', flexDirection: 'column',
@ -56,6 +61,27 @@ export const InputBox = styled('input', {
}, },
}); });
export const Textarea = styled('textarea', {
all: 'unset',
fontWeight: '300',
border: '1px solid $gray6',
padding: '0.5rem',
borderRadius: '0.3rem',
backgroundColor: '$gray2',
'&:hover': {
borderColor: '$teal7',
},
'&:focus': {
borderColor: '$teal7',
backgroundColor: '$gray3',
},
'&:disabled': {
backgroundColor: '$gray4',
borderColor: '$gray5',
color: '$gray8',
},
});
export const ButtonGroup = styled('div', { export const ButtonGroup = styled('div', {
display: 'flex', display: 'flex',
gap: '0.5rem', gap: '0.5rem',
@ -68,25 +94,52 @@ export const Button = styled('button', {
padding: '0.5rem 1rem', padding: '0.5rem 1rem',
borderRadius: '0.3rem', borderRadius: '0.3rem',
fontSize: '1.1rem', fontSize: '1.1rem',
border: '1px solid $teal6', border: '1px solid $gray6',
backgroundColor: '$teal4', backgroundColor: '$gray4',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
gap: '0.5rem', gap: '0.5rem',
'&:hover': { '&:hover': {
backgroundColor: '$teal5', backgroundColor: '$gray5',
borderColor: '$gray8',
}, },
'&:active': { '&:active': {
background: '$teal6', background: '$gray6',
}, },
transition: 'all 0.2s', transition: 'all 0.2s',
variants: { variants: {
styling: {
link: {
backgroundColor: 'transparent',
border: 'none',
color: '$teal11',
textDecoration: 'underline',
},
},
size: {
small: {
padding: '0.3rem 0.5rem',
fontSize: '0.9rem',
},
},
variation: { variation: {
primary: {
border: '1px solid $teal6',
backgroundColor: '$teal4',
'&:hover': {
backgroundColor: '$teal5',
borderColor: '$teal8',
},
'&:active': {
background: '$teal6',
},
},
success: { success: {
border: '1px solid $grass6', border: '1px solid $grass6',
backgroundColor: '$grass4', backgroundColor: '$grass4',
'&:hover': { '&:hover': {
backgroundColor: '$grass5', backgroundColor: '$grass5',
borderColor: '$grass8',
}, },
'&:active': { '&:active': {
background: '$grass6', background: '$grass6',
@ -97,6 +150,18 @@ export const Button = styled('button', {
backgroundColor: '$red4', backgroundColor: '$red4',
'&:hover': { '&:hover': {
backgroundColor: '$red5', backgroundColor: '$red5',
borderColor: '$red8',
},
'&:active': {
background: '$red6',
},
},
danger: {
border: '1px solid $red6',
backgroundColor: '$red4',
'&:hover': {
backgroundColor: '$red5',
borderColor: '$red8',
}, },
'&:active': { '&:active': {
background: '$red6', background: '$red6',
@ -106,6 +171,28 @@ export const Button = styled('button', {
}, },
}); });
export const ComboBox = styled('select', {
margin: 0,
color: '$teal13',
fontWeight: '300',
border: '1px solid $gray6',
padding: '0.5rem',
borderRadius: '0.3rem',
backgroundColor: '$gray2',
'&:hover': {
borderColor: '$teal7',
},
'&:focus': {
borderColor: '$teal7',
backgroundColor: '$gray3',
},
'&:disabled': {
backgroundColor: '$gray4',
borderColor: '$gray5',
color: '$gray8',
},
});
export const Checkbox = styled(CheckboxPrimitive.Root, { export const Checkbox = styled(CheckboxPrimitive.Root, {
all: 'unset', all: 'unset',
width: 25, width: 25,

View file

@ -1,6 +1,7 @@
export * from './theme';
export * from './brand'; export * from './brand';
export * from './dialog';
export * from './forms'; export * from './forms';
export * from './pages'; export * from './pages';
export * from './tabs'; export * from './tabs';
export * from './theme';
export * from './utils'; export * from './utils';

View file

@ -4,6 +4,7 @@ import {
yellowDark, yellowDark,
grassDark, grassDark,
redDark, redDark,
blackA,
} from '@radix-ui/colors'; } from '@radix-ui/colors';
import { globalCss, createStitches } from '@stitches/react'; import { globalCss, createStitches } from '@stitches/react';
@ -34,6 +35,7 @@ export const { styled, theme } = createStitches({
...yellowDark, ...yellowDark,
...grassDark, ...grassDark,
...redDark, ...redDark,
...blackA,
}, },
}, },
}); });