Compare commits

...
Author SHA1 Message Date
bflatastic d91a9b2259 added shared settings modal to editor2d 2025-04-22 00:56:18 +07:00
bflatastic 243a19e7a9 select component update 2025-04-22 00:34:23 +07:00
bflatastic 5e3b895041 added shared select component 2025-04-22 00:24:54 +07:00
bflatastic 99d7783e35 add reusable components for settings modal 2025-04-22 00:05:35 +07:00
bflatastic 2fa97806a5 settings modal 2025-04-21 15:01:43 +07:00
90 changed files with 2378 additions and 49 deletions
+33 -22
View File
@@ -2,6 +2,38 @@
@tailwind components;
@tailwind utilities;
* {
scrollbar-width: thin;
scrollbar-color: #363636 #171717;
}
/* Works on Chrome, Edge, and Safari */
*::-webkit-scrollbar {
width: 8px;
}
*::-webkit-scrollbar-track {
background: #171717;
}
*::-webkit-scrollbar-thumb {
background-color: #363636;
border-radius: 4px;
border: 2px solid #171717;
}
/* Chrome, Safari, Edge, Opera */
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* Firefox */
input[type="number"] {
-moz-appearance: textfield;
}
@layer base {
:root {
@apply select-none bg-ui-background font-sans text-white;
@@ -27,28 +59,7 @@
background-size: 36px 36px;
@apply bg-center;
}
h1 {
@apply text-3xl font-medium md:text-5xl;
}
h2 {
@apply text-2xl font-medium md:text-4xl;
}
h3 {
@apply text-xl font-medium md:text-3xl;
}
h4 {
@apply text-lg font-medium;
}
h5,
label {
@apply text-base;
}
h6 {
@apply text-sm;
}
p {
@apply text-sm font-normal;
}
a {
@apply font-medium text-primary-400 hover:text-primary-300;
}
@@ -1,16 +1,32 @@
import { faGear } from "@fortawesome/pro-solid-svg-icons";
import { twMerge } from "tailwind-merge";
import { Login } from "~/app/pages/login";
import { Activity } from "~/components/ui/Activity/Activity";
// import { paperWrapperStyles } from "~/components/styles";
// import { faPlus, faQuestion } from "@fortawesome/pro-solid-svg-icons";
// import { ToolbarButton } from "~/components/features/ToolbarButton";
import { Button } from "@storyteller/ui-button";
import { SettingsModal } from "@storyteller/ui-settings-modal";
import { useState } from "react";
export const ToolbarTopRight = () => {
const [isSettingsModalOpen, setIsSettingsModalOpen] = useState(false);
return (
<div
className={twMerge("relative z-50 flex h-fit w-fit items-center gap-2")}
>
<Activity />
</div>
<>
<div
className={twMerge("relative z-50 flex h-fit w-fit items-center gap-2")}
>
<Button
icon={faGear}
className="h-[42px] w-[42px] bg-[#5F5F68]/60 backdrop-blur-lg hover:bg-[#5F5F68]/90"
onClick={() => setIsSettingsModalOpen(true)}
/>
<Activity />
</div>
<SettingsModal
isOpen={isSettingsModalOpen}
onClose={() => setIsSettingsModalOpen(false)}
/>
</>
);
};
+35 -3
View File
@@ -4,7 +4,11 @@ import defaultTheme from "tailwindcss/defaultTheme";
import { storytellerColors } from "./tailwind.stcolors";
export default {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
"../../libs/components/**/*.{js,jsx,ts,tsx}",
],
theme: {
extend: {
fontFamily: {
@@ -44,14 +48,42 @@ export default {
info: colors.sky[500],
// brand colors
primary: storytellerColors.azureRadiance,
secondary: storytellerColors.gunpowder,
primary: {
DEFAULT: "#2d81ff",
50: "#eef6ff",
100: "#d9ebff",
200: "#bcdcff",
300: "#8ec7ff",
400: "#59a7ff",
500: "#2d81ff",
600: "#1b63f5",
700: "#144ee1",
800: "#173fb6",
900: "#19398f",
950: "#142457",
},
secondary: {
DEFAULT: "#3E3E41",
50: "#f6f6f6",
100: "#e7e7e7",
200: "#d1d1d1",
300: "#b0b0b0",
400: "#888888",
500: "#6d6d6d",
600: "#5d5d5d",
700: "#4f4f4f",
800: "#454545",
900: "#3d3d3d",
950: "#3E3E41",
},
tertiary: storytellerColors.aquamarineBlue,
ui: {
background: "#101014",
panel: "#3E3E41",
border: "#FFFFFF0D",
"panel-border": "#3F3F3F",
controls: "#3E3E41",
},
},
+11 -1
View File
@@ -24,5 +24,15 @@
"@ApiManager/*": ["./src/Classes/ApiManager/*"]
}
},
"include": ["src"]
"include": [
"src"
],
"references": [
{
"path": "../../libs/components/settings-modal/tsconfig.lib.json"
},
{
"path": "../../libs/components/button/tsconfig.lib.json"
}
]
}
+12 -2
View File
@@ -1,7 +1,17 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
{
"path": "../../libs/components/settings-modal"
},
{
"path": "../../libs/components/button"
},
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.node.json"
}
]
}
@@ -52,7 +52,7 @@ export const Input = React.forwardRef(
id={id ? id : label ? kebabCase(label) : undefined}
className={twMerge(
"h-10 w-full rounded-lg bg-[#242424] px-3 py-2.5 text-white placeholder-white/50 outline-none",
"border border-[#363636] transition-all duration-150 ease-in-out hover:border-brand-primary/60 focus:border-brand-primary focus:!outline-none",
"hover:border-primary/60 focus:border-primary border border-[#363636] transition-all duration-150 ease-in-out focus:!outline-none",
icon && "pl-10",
isError && "outline-red focus:outline-red",
inputClassName,
@@ -1,6 +1,8 @@
import { useState } from "react";
import {
faChevronLeft,
faChevronRight,
faGear,
faImages,
} from "@fortawesome/pro-solid-svg-icons";
import { useLocation, useParams } from "react-router-dom";
@@ -10,7 +12,7 @@ import { SceneTitleInput } from "./SceneTitleInput";
import { getCurrentLocationWithoutParams } from "~/utilities";
import { Activity } from "~/pages/PageEnigma/comps/GenerateModals/Activity";
import { LibraryModal } from "~/pages/PageEnigma/comps/LibraryModal/LibraryModal";
import { useState } from "react";
import { SettingsModal } from "@storyteller/ui-settings-modal";
function isEditorPath(path: string) {
if (path === "/") return true;
@@ -28,6 +30,7 @@ export const TopBar = ({ pageName }: Props) => {
);
const [isLibraryModalOpen, setIsLibraryModalOpen] = useState(false);
const [activeLibraryTab, setActiveLibraryTab] = useState("my-media");
const [isSettingsModalOpen, setIsSettingsModalOpen] = useState(false);
const handleClick = () => {
window.location.href = "https://storyteller-2d.netlify.app/";
@@ -72,6 +75,12 @@ export const TopBar = ({ pageName }: Props) => {
<div className="flex justify-end gap-3.5">
<div className="flex gap-2">
<Button
variant="secondary"
icon={faGear}
className="h-[38px] w-[38px]"
onClick={() => setIsSettingsModalOpen(true)}
/>
<Button
variant="secondary"
icon={faImages}
@@ -79,6 +88,7 @@ export const TopBar = ({ pageName }: Props) => {
>
My Gallery
</Button>
<Activity />
</div>
<div className="flex justify-end gap-2">
@@ -88,6 +98,11 @@ export const TopBar = ({ pageName }: Props) => {
</nav>
</header>
<SettingsModal
isOpen={isSettingsModalOpen}
onClose={() => setIsSettingsModalOpen(false)}
/>
<LibraryModal
isOpen={isLibraryModalOpen}
onClose={() => setIsLibraryModalOpen(false)}
@@ -0,0 +1 @@
export { SettingsModal } from "./SettingsModal";
+32 -13
View File
@@ -6,6 +6,7 @@ export default {
"./app/index.html",
"./app/src/**/*.{js,jsx,ts,tsx}",
"./app/src/*.{js,jsx,ts,tsx}",
"../../libs/components/**/*.{js,jsx,ts,tsx}",
],
theme: {
extend: {
@@ -25,7 +26,37 @@ export default {
colors: {
transparent: "transparent",
current: "currentColor",
white: colors.white,
gray: colors.gray,
black: colors.black,
primary: {
DEFAULT: "#2d81ff",
"50": "#eef6ff",
"100": "#d9ebff",
"200": "#bcdcff",
"300": "#8ec7ff",
"400": "#59a7ff",
"500": "#2d81ff",
"600": "#1b63f5",
"700": "#144ee1",
"800": "#173fb6",
"900": "#19398f",
"950": "#142457",
},
secondary: {
DEFAULT: "#3E3E41",
"50": "#f6f6f6",
"100": "#e7e7e7",
"200": "#d1d1d1",
"300": "#b0b0b0",
"400": "#888888",
"500": "#6d6d6d",
"600": "#5d5d5d",
"700": "#4f4f4f",
"800": "#454545",
"900": "#3d3d3d",
"950": "#3E3E41",
},
"brand-primary": {
//sunglo
DEFAULT: "#2d81ff", //=500
@@ -85,7 +116,6 @@ export default {
"900": "#64373f",
"950": "#361b1e",
},
success: {
//apple
DEFAULT: "#40ad48", //=500
@@ -101,7 +131,6 @@ export default {
"900": "#1f4823",
"950": "#0c270f",
},
action: {
DEFAULT: "#3E3E41",
"50": "#f6f6f6",
@@ -116,7 +145,6 @@ export default {
"900": "#3d3d3d",
"950": "#3E3E41",
},
character: {
unselected: "#46527C",
selected: "#6384F4",
@@ -169,7 +197,6 @@ export default {
cannotDrop: "#904948",
wrapper: "#39394d",
},
ui: {
background: "#242424",
panel: "#1F1F1F",
@@ -178,7 +205,6 @@ export default {
"controls-button": "#3E3E41",
divider: "#515168",
},
media: {
"audio-tts": "#6AAA88",
"audio-v2v": "#6691B9",
@@ -186,29 +212,22 @@ export default {
"audio-demo": "#B96675",
"is-new": "#FFDC26",
},
inference: {
pending: "#6b728b",
generating: "#46527C",
error: "#924C4B",
},
"axis-x": "#D33242",
"axis-y": "#308752",
"axis-z": "#2E70FF",
red: "#D33242",
green: "#308752",
blue: "#2E70FF",
facebook: "#4267b2",
reddit: "#ff5700",
whatsapp: "#25d366",
x: "#000000",
email: "#858585",
white: colors.white,
gray: colors.gray,
black: colors.black,
},
},
plugins: [],
+3
View File
@@ -23,6 +23,9 @@
"references": [
{
"path": "../../libs/components/toaster"
},
{
"path": "../../libs/components/settings-modal"
}
]
}
+12
View File
@@ -0,0 +1,12 @@
{
"presets": [
[
"@nx/react/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"plugins": []
}
@@ -0,0 +1,7 @@
# @storyteller/ui-button
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test @storyteller/ui-button` to execute the unit tests via [Vitest](https://vitest.dev/).
@@ -0,0 +1,17 @@
{
"name": "@storyteller/ui-button",
"version": "0.0.1",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"development": "./src/index.ts",
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"default": "./dist/index.js"
}
}
}
@@ -0,0 +1,2 @@
export * from './lib/button';
@@ -0,0 +1,7 @@
/*
* Replace this with your own classes
*
* e.g.
* .container {
* }
*/
@@ -0,0 +1,12 @@
import { render } from '@testing-library/react';
import Button from './button';
describe('Button', () => {
it('should render successfully', () => {
const { baseElement } = render(<Button />);
expect(baseElement).toBeTruthy();
});
});
@@ -0,0 +1,108 @@
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge";
import { ButtonHTMLAttributes } from "react";
import { faSpinnerThird } from "@fortawesome/pro-solid-svg-icons";
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
icon?: IconDefinition;
iconClassName?: string;
iconFlip?: boolean;
htmlFor?: string;
variant?: "primary" | "secondary" | "action";
loading?: boolean;
}
export const Button = ({
icon,
iconClassName,
children,
className: propsClassName,
htmlFor,
variant: propsVariant = "primary",
disabled,
iconFlip = false,
loading,
...rest
}: ButtonProps) => {
function getVariantClassNames(variant: string) {
switch (variant) {
case "secondary": {
return "bg-secondary hover:bg-secondary-700 text-white focus-visible:outline-secondary";
}
case "action": {
return "bg-action hover:bg-action-900 text-white focus-visible:outline-action";
}
case "primary":
default: {
return "bg-primary hover:bg-primary-400 text-white focus-visible:outline-primary-600";
}
}
}
const disabledClass = twMerge(
disabled || loading ? "opacity-50 pointer-events-none" : ""
);
const className = twMerge(
"text-sm font-medium rounded-lg px-3 py-2 border border-transparent shadow-sm focus-visible:outline focus-visible:outline-0 focus-visible:outline-offset-0 transition-all duration-150 flex gap-2 items-center justify-center",
getVariantClassNames(propsVariant),
propsClassName,
disabledClass
);
if (htmlFor) {
return (
<label className={className} htmlFor={htmlFor} style={rest.style}>
{loading && !iconFlip ? (
<FontAwesomeIcon icon={faSpinnerThird} className="animate-spin" />
) : (
<>
{icon && !iconFlip ? (
<FontAwesomeIcon icon={icon} className={iconClassName} />
) : null}
</>
)}
{children}
{loading && iconFlip ? (
<FontAwesomeIcon icon={faSpinnerThird} className="animate-spin" />
) : (
<>
{icon && iconFlip ? (
<FontAwesomeIcon icon={icon} className={iconClassName} />
) : null}
</>
)}
</label>
);
}
return (
<button
className={className}
disabled={disabled || loading}
{...{ ...rest, htmlFor }}
>
{loading && !iconFlip ? (
<FontAwesomeIcon icon={faSpinnerThird} className="animate-spin" />
) : (
<>
{icon && !iconFlip ? (
<FontAwesomeIcon icon={icon} className={iconClassName} />
) : null}
</>
)}
{children}
{loading && iconFlip ? (
<FontAwesomeIcon icon={faSpinnerThird} className="animate-spin" />
) : (
<>
{icon && iconFlip ? (
<FontAwesomeIcon icon={icon} className={iconClassName} />
) : null}
</>
)}
</button>
);
};
export default Button;
@@ -0,0 +1,13 @@
{
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"extends": "../../../tsconfig.base.json"
}
@@ -0,0 +1,45 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"types": [
"node",
"@nx/react/typings/cssmodule.d.ts",
"@nx/react/typings/image.d.ts",
"vite/client"
],
"rootDir": "src",
"jsx": "react-jsx",
"tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo"
},
"exclude": [
"out-tsc",
"dist",
"**/*.spec.ts",
"**/*.test.ts",
"**/*.spec.tsx",
"**/*.test.tsx",
"**/*.spec.js",
"**/*.test.js",
"**/*.spec.jsx",
"**/*.test.jsx",
"vite.config.ts",
"vite.config.mts",
"vitest.config.ts",
"vitest.config.mts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx"
],
"include": [
"src/**/*.js",
"src/**/*.jsx",
"src/**/*.ts",
"src/**/*.tsx"
]
}
@@ -0,0 +1,34 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./out-tsc/vitest",
"types": [
"vitest/globals",
"vitest/importMeta",
"vite/client",
"node",
"vitest"
],
"jsx": "react-jsx"
},
"include": [
"vite.config.ts",
"vite.config.mts",
"vitest.config.ts",
"vitest.config.mts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx",
"src/**/*.d.ts"
],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}
@@ -0,0 +1,49 @@
/// <reference types='vitest' />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import dts from 'vite-plugin-dts';
import * as path from 'path';
export default defineConfig(() => ({
root: __dirname,
cacheDir: '../../../node_modules/.vite/libs/components/button',
plugins: [react(), dts({ entryRoot: 'src', tsconfigPath: path.join(__dirname, 'tsconfig.lib.json') })],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
outDir: './dist',
emptyOutDir: true,
reportCompressedSize: true,
commonjsOptions: {
transformMixedEsModules: true,
},
lib: {
// Could also be a dictionary or array of multiple entry points.
entry: 'src/index.ts',
name: '@storyteller/ui-button',
fileName: 'index',
// Change this to the formats you want to support.
// Don't forget to update your package.json as well.
formats: ['es' as const]
},
rollupOptions: {
// External packages that should not be bundled into your library.
external: ['react','react-dom','react/jsx-runtime']
},
},
test: {
watch: false,
globals: true,
environment: 'jsdom',
include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: './test-output/vitest/coverage',
provider: 'v8' as const,
}
},
}));
@@ -0,0 +1,12 @@
{
"presets": [
[
"@nx/react/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"plugins": []
}
@@ -0,0 +1,7 @@
# @storyteller/ui-close-button
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test @storyteller/ui-close-button` to execute the unit tests via [Vitest](https://vitest.dev/).
@@ -0,0 +1,17 @@
{
"name": "@storyteller/ui-close-button",
"version": "0.0.1",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"development": "./src/index.ts",
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"default": "./dist/index.js"
}
}
}
@@ -0,0 +1,2 @@
export * from './lib/close-button';
@@ -0,0 +1,7 @@
/*
* Replace this with your own classes
*
* e.g.
* .container {
* }
*/
@@ -0,0 +1,12 @@
import { render } from '@testing-library/react';
import CloseButton from './close-button';
describe('CloseButton', () => {
it('should render successfully', () => {
const { baseElement } = render(<CloseButton />);
expect(baseElement).toBeTruthy();
});
});
@@ -0,0 +1,36 @@
import { faXmark } from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge";
interface CloseButtonProps {
onClick: () => void;
className?: string;
size?: "sm" | "md" | "lg";
}
export const CloseButton = ({
onClick,
className,
size = "md",
}: CloseButtonProps) => {
const sizeClasses = {
sm: "h-5 w-5 text-sm",
md: "h-7 w-7 text-md",
lg: "h-9 w-9 text-xl",
};
return (
<button
onClick={onClick}
className={twMerge(
"flex items-center justify-center rounded-full bg-black/40 text-white/60 transition-all hover:bg-black/70 hover:text-white",
sizeClasses[size],
className
)}
>
<FontAwesomeIcon icon={faXmark} />
</button>
);
};
export default CloseButton;
@@ -0,0 +1,13 @@
{
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"extends": "../../../tsconfig.base.json"
}
@@ -0,0 +1,45 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"types": [
"node",
"@nx/react/typings/cssmodule.d.ts",
"@nx/react/typings/image.d.ts",
"vite/client"
],
"rootDir": "src",
"jsx": "react-jsx",
"tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo"
},
"exclude": [
"out-tsc",
"dist",
"**/*.spec.ts",
"**/*.test.ts",
"**/*.spec.tsx",
"**/*.test.tsx",
"**/*.spec.js",
"**/*.test.js",
"**/*.spec.jsx",
"**/*.test.jsx",
"vite.config.ts",
"vite.config.mts",
"vitest.config.ts",
"vitest.config.mts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx"
],
"include": [
"src/**/*.js",
"src/**/*.jsx",
"src/**/*.ts",
"src/**/*.tsx"
]
}
@@ -0,0 +1,34 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./out-tsc/vitest",
"types": [
"vitest/globals",
"vitest/importMeta",
"vite/client",
"node",
"vitest"
],
"jsx": "react-jsx"
},
"include": [
"vite.config.ts",
"vite.config.mts",
"vitest.config.ts",
"vitest.config.mts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx",
"src/**/*.d.ts"
],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}
@@ -0,0 +1,49 @@
/// <reference types='vitest' />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import dts from 'vite-plugin-dts';
import * as path from 'path';
export default defineConfig(() => ({
root: __dirname,
cacheDir: '../../../node_modules/.vite/libs/components/close-button',
plugins: [react(), dts({ entryRoot: 'src', tsconfigPath: path.join(__dirname, 'tsconfig.lib.json') })],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
outDir: './dist',
emptyOutDir: true,
reportCompressedSize: true,
commonjsOptions: {
transformMixedEsModules: true,
},
lib: {
// Could also be a dictionary or array of multiple entry points.
entry: 'src/index.ts',
name: '@storyteller/ui-close-button',
fileName: 'index',
// Change this to the formats you want to support.
// Don't forget to update your package.json as well.
formats: ['es' as const]
},
rollupOptions: {
// External packages that should not be bundled into your library.
external: ['react','react-dom','react/jsx-runtime']
},
},
test: {
watch: false,
globals: true,
environment: 'jsdom',
include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: './test-output/vitest/coverage',
provider: 'v8' as const,
}
},
}));
+12
View File
@@ -0,0 +1,12 @@
{
"presets": [
[
"@nx/react/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"plugins": []
}
+7
View File
@@ -0,0 +1,7 @@
# @storyteller/ui-input
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test @storyteller/ui-input` to execute the unit tests via [Vitest](https://vitest.dev/).
@@ -0,0 +1,17 @@
{
"name": "@storyteller/ui-input",
"version": "0.0.1",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"development": "./src/index.ts",
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"default": "./dist/index.js"
}
}
}
@@ -0,0 +1,2 @@
export * from './lib/input';
@@ -0,0 +1,7 @@
/*
* Replace this with your own classes
*
* e.g.
* .container {
* }
*/
@@ -0,0 +1,12 @@
import { render } from '@testing-library/react';
import Input from './input';
describe('Input', () => {
it('should render successfully', () => {
const { baseElement } = render(<Input />);
expect(baseElement).toBeTruthy();
});
});
@@ -0,0 +1,77 @@
import React from "react";
import { twMerge } from "tailwind-merge";
import { IconDefinition } from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Label } from "@storyteller/ui-label";
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
inputClassName?: string;
iconClassName?: string;
label?: string;
icon?: IconDefinition;
isError?: boolean;
errorMessage?: string;
}
export const Input = React.forwardRef(
(
{
label,
icon,
inputClassName,
iconClassName,
className,
id,
isError,
onBlur,
onFocus,
errorMessage,
...rest
}: InputProps,
ref: React.ForwardedRef<HTMLInputElement>
) => {
return (
<div className={twMerge("flex flex-col", className)}>
{label && <Label htmlFor={id ? id : label}>{label}</Label>}
<div className="relative w-full">
{icon && (
<FontAwesomeIcon
icon={icon}
className={twMerge("text-md absolute pl-3 pt-3", iconClassName)}
/>
)}
<input
ref={ref}
id={id ? id : label ? label : undefined}
className={twMerge(
"h-10 w-full rounded-lg bg-[#242424] px-3 py-2.5 text-white placeholder-white/50 outline-none",
"border border-[#363636] transition-all duration-150 ease-in-out hover:border-brand-primary/60 focus:border-brand-primary focus:!outline-none",
icon && "pl-10",
isError && "outline-red focus:outline-red",
inputClassName
)}
onFocus={(e: React.FocusEvent<HTMLInputElement>) => {
if (onFocus) {
onFocus(e);
}
}}
onBlur={(e: React.FocusEvent<HTMLInputElement>) => {
if (onBlur) {
onBlur(e);
}
}}
{...rest}
/>
{errorMessage && (
<h6 className="absolute z-10 text-red">{errorMessage}</h6>
)}
</div>
</div>
);
}
);
Input.displayName = "Input";
export default Input;
@@ -0,0 +1,16 @@
{
"files": [],
"include": [],
"references": [
{
"path": "../label"
},
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"extends": "../../../tsconfig.base.json"
}
@@ -0,0 +1,50 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"types": [
"node",
"@nx/react/typings/cssmodule.d.ts",
"@nx/react/typings/image.d.ts",
"vite/client"
],
"rootDir": "src",
"jsx": "react-jsx",
"tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo"
},
"exclude": [
"out-tsc",
"dist",
"**/*.spec.ts",
"**/*.test.ts",
"**/*.spec.tsx",
"**/*.test.tsx",
"**/*.spec.js",
"**/*.test.js",
"**/*.spec.jsx",
"**/*.test.jsx",
"vite.config.ts",
"vite.config.mts",
"vitest.config.ts",
"vitest.config.mts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx"
],
"include": [
"src/**/*.js",
"src/**/*.jsx",
"src/**/*.ts",
"src/**/*.tsx"
],
"references": [
{
"path": "../label/tsconfig.lib.json"
}
]
}
@@ -0,0 +1,34 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./out-tsc/vitest",
"types": [
"vitest/globals",
"vitest/importMeta",
"vite/client",
"node",
"vitest"
],
"jsx": "react-jsx"
},
"include": [
"vite.config.ts",
"vite.config.mts",
"vitest.config.ts",
"vitest.config.mts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx",
"src/**/*.d.ts"
],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}
@@ -0,0 +1,49 @@
/// <reference types='vitest' />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import dts from 'vite-plugin-dts';
import * as path from 'path';
export default defineConfig(() => ({
root: __dirname,
cacheDir: '../../../node_modules/.vite/libs/components/input',
plugins: [react(), dts({ entryRoot: 'src', tsconfigPath: path.join(__dirname, 'tsconfig.lib.json') })],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
outDir: './dist',
emptyOutDir: true,
reportCompressedSize: true,
commonjsOptions: {
transformMixedEsModules: true,
},
lib: {
// Could also be a dictionary or array of multiple entry points.
entry: 'src/index.ts',
name: '@storyteller/ui-input',
fileName: 'index',
// Change this to the formats you want to support.
// Don't forget to update your package.json as well.
formats: ['es' as const]
},
rollupOptions: {
// External packages that should not be bundled into your library.
external: ['react','react-dom','react/jsx-runtime']
},
},
test: {
watch: false,
globals: true,
environment: 'jsdom',
include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: './test-output/vitest/coverage',
provider: 'v8' as const,
}
},
}));
+12
View File
@@ -0,0 +1,12 @@
{
"presets": [
[
"@nx/react/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"plugins": []
}
+7
View File
@@ -0,0 +1,7 @@
# @storyteller/ui-label
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test @storyteller/ui-label` to execute the unit tests via [Vitest](https://vitest.dev/).
@@ -0,0 +1,17 @@
{
"name": "@storyteller/ui-label",
"version": "0.0.1",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"development": "./src/index.ts",
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"default": "./dist/index.js"
}
}
}
@@ -0,0 +1,2 @@
export * from './lib/label';
@@ -0,0 +1,7 @@
/*
* Replace this with your own classes
*
* e.g.
* .container {
* }
*/
@@ -0,0 +1,12 @@
import { render } from '@testing-library/react';
import Label from './label';
describe('Label', () => {
it('should render successfully', () => {
const { baseElement } = render(<Label />);
expect(baseElement).toBeTruthy();
});
});
@@ -0,0 +1,25 @@
import { LabelHTMLAttributes, ReactNode } from "react";
import { twMerge } from "tailwind-merge";
interface LabelProps extends LabelHTMLAttributes<HTMLLabelElement> {
className?: string;
children: ReactNode;
required?: boolean;
}
export const Label = ({
className,
children,
required,
...rest
}: LabelProps) => (
<label
className={twMerge("text-white mb-2 text-[15px] font-medium", className)}
{...rest}
>
{children}
{required && <span className="ml-0.5 text-[#ff6467]">*</span>}
</label>
);
export default Label;
@@ -0,0 +1,13 @@
{
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"extends": "../../../tsconfig.base.json"
}
@@ -0,0 +1,45 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"types": [
"node",
"@nx/react/typings/cssmodule.d.ts",
"@nx/react/typings/image.d.ts",
"vite/client"
],
"rootDir": "src",
"jsx": "react-jsx",
"tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo"
},
"exclude": [
"out-tsc",
"dist",
"**/*.spec.ts",
"**/*.test.ts",
"**/*.spec.tsx",
"**/*.test.tsx",
"**/*.spec.js",
"**/*.test.js",
"**/*.spec.jsx",
"**/*.test.jsx",
"vite.config.ts",
"vite.config.mts",
"vitest.config.ts",
"vitest.config.mts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx"
],
"include": [
"src/**/*.js",
"src/**/*.jsx",
"src/**/*.ts",
"src/**/*.tsx"
]
}
@@ -0,0 +1,34 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./out-tsc/vitest",
"types": [
"vitest/globals",
"vitest/importMeta",
"vite/client",
"node",
"vitest"
],
"jsx": "react-jsx"
},
"include": [
"vite.config.ts",
"vite.config.mts",
"vitest.config.ts",
"vitest.config.mts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx",
"src/**/*.d.ts"
],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}
@@ -0,0 +1,49 @@
/// <reference types='vitest' />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import dts from 'vite-plugin-dts';
import * as path from 'path';
export default defineConfig(() => ({
root: __dirname,
cacheDir: '../../../node_modules/.vite/libs/components/label',
plugins: [react(), dts({ entryRoot: 'src', tsconfigPath: path.join(__dirname, 'tsconfig.lib.json') })],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
outDir: './dist',
emptyOutDir: true,
reportCompressedSize: true,
commonjsOptions: {
transformMixedEsModules: true,
},
lib: {
// Could also be a dictionary or array of multiple entry points.
entry: 'src/index.ts',
name: '@storyteller/ui-label',
fileName: 'index',
// Change this to the formats you want to support.
// Don't forget to update your package.json as well.
formats: ['es' as const]
},
rollupOptions: {
// External packages that should not be bundled into your library.
external: ['react','react-dom','react/jsx-runtime']
},
},
test: {
watch: false,
globals: true,
environment: 'jsdom',
include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: './test-output/vitest/coverage',
provider: 'v8' as const,
}
},
}));
+12
View File
@@ -0,0 +1,12 @@
{
"presets": [
[
"@nx/react/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"plugins": []
}
+7
View File
@@ -0,0 +1,7 @@
# @storyteller/ui-modal
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test @storyteller/ui-modal` to execute the unit tests via [Vitest](https://vitest.dev/).
@@ -0,0 +1,17 @@
{
"name": "@storyteller/ui-modal",
"version": "0.0.1",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"development": "./src/index.ts",
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"default": "./dist/index.js"
}
}
}
@@ -0,0 +1,2 @@
export * from './lib/modal';
@@ -0,0 +1,7 @@
/*
* Replace this with your own classes
*
* e.g.
* .container {
* }
*/
@@ -0,0 +1,12 @@
import { render } from '@testing-library/react';
import Modal from './modal';
describe('Modal', () => {
it('should render successfully', () => {
const { baseElement } = render(<Modal />);
expect(baseElement).toBeTruthy();
});
});
@@ -0,0 +1,138 @@
import { Fragment, ReactNode } from "react";
import {
Dialog,
DialogPanel,
DialogTitle,
Transition,
TransitionChild,
} from "@headlessui/react";
import { twMerge } from "tailwind-merge";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { IconDefinition } from "@fortawesome/pro-solid-svg-icons";
import { CloseButton } from "@storyteller/ui-close-button";
const DialogBackdrop = ({ className }: { className?: string }) => {
return (
<TransitionChild
as="div"
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div
className={twMerge("fixed inset-0 bg-black/60", className)}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
/>
</TransitionChild>
);
};
export const Modal = ({
isOpen,
title,
titleIcon,
onTitleIconClick,
onClose,
className,
backdropClassName,
width,
children,
childPadding = true,
titleIconClassName,
showClose = true,
}: {
isOpen: boolean;
title?: ReactNode;
titleIcon?: IconDefinition;
onTitleIconClick?: () => void;
titleIconClassName?: string;
onClose: () => void;
className?: string;
backdropClassName?: string;
width?: number;
children: ReactNode;
childPadding?: boolean;
showClose?: boolean;
}) => {
return (
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-[70]" onClose={onClose}>
<DialogBackdrop className={backdropClassName} />
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<TransitionChild
as="div"
enter="ease-out duration-200"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
className={twMerge(
"w-full max-w-lg transform rounded-xl relative",
"border border-ui-panel-border bg-[#2C2C2C]",
"text-left align-middle shadow-xl transition-all",
childPadding ? "p-4" : "",
className
)}
>
<DialogPanel
className="w-full relative"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
{title && (
<DialogTitle
as="div"
className="mb-5 flex justify-between pb-0 text-xl font-bold text-white"
>
<>
{onTitleIconClick ? (
<button
className="flex items-center gap-3"
onClick={onTitleIconClick}
>
{titleIcon && (
<FontAwesomeIcon
icon={titleIcon}
className={titleIconClassName}
/>
)}
{title}
</button>
) : (
<div className="flex items-center gap-3">
{titleIcon && (
<FontAwesomeIcon
icon={titleIcon}
className={titleIconClassName}
/>
)}
{title}
</div>
)}
</>
</DialogTitle>
)}
<div className={`h-full`.trim()}>{children}</div>
</DialogPanel>
{showClose && (
<div className="absolute top-0 right-0 p-2.5">
<CloseButton onClick={onClose} />
</div>
)}
</TransitionChild>
</div>
</div>
</Dialog>
</Transition>
);
};
@@ -0,0 +1,16 @@
{
"files": [],
"include": [],
"references": [
{
"path": "../close-button"
},
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"extends": "../../../tsconfig.base.json"
}
@@ -0,0 +1,50 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"types": [
"node",
"@nx/react/typings/cssmodule.d.ts",
"@nx/react/typings/image.d.ts",
"vite/client"
],
"rootDir": "src",
"jsx": "react-jsx",
"tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo"
},
"exclude": [
"out-tsc",
"dist",
"**/*.spec.ts",
"**/*.test.ts",
"**/*.spec.tsx",
"**/*.test.tsx",
"**/*.spec.js",
"**/*.test.js",
"**/*.spec.jsx",
"**/*.test.jsx",
"vite.config.ts",
"vite.config.mts",
"vitest.config.ts",
"vitest.config.mts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx"
],
"include": [
"src/**/*.js",
"src/**/*.jsx",
"src/**/*.ts",
"src/**/*.tsx"
],
"references": [
{
"path": "../close-button/tsconfig.lib.json"
}
]
}
@@ -0,0 +1,34 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./out-tsc/vitest",
"types": [
"vitest/globals",
"vitest/importMeta",
"vite/client",
"node",
"vitest"
],
"jsx": "react-jsx"
},
"include": [
"vite.config.ts",
"vite.config.mts",
"vitest.config.ts",
"vitest.config.mts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx",
"src/**/*.d.ts"
],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}
@@ -0,0 +1,49 @@
/// <reference types='vitest' />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import dts from 'vite-plugin-dts';
import * as path from 'path';
export default defineConfig(() => ({
root: __dirname,
cacheDir: '../../../node_modules/.vite/libs/components/modal',
plugins: [react(), dts({ entryRoot: 'src', tsconfigPath: path.join(__dirname, 'tsconfig.lib.json') })],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
outDir: './dist',
emptyOutDir: true,
reportCompressedSize: true,
commonjsOptions: {
transformMixedEsModules: true,
},
lib: {
// Could also be a dictionary or array of multiple entry points.
entry: 'src/index.ts',
name: '@storyteller/ui-modal',
fileName: 'index',
// Change this to the formats you want to support.
// Don't forget to update your package.json as well.
formats: ['es' as const]
},
rollupOptions: {
// External packages that should not be bundled into your library.
external: ['react','react-dom','react/jsx-runtime']
},
},
test: {
watch: false,
globals: true,
environment: 'jsdom',
include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: './test-output/vitest/coverage',
provider: 'v8' as const,
}
},
}));
+12
View File
@@ -0,0 +1,12 @@
{
"presets": [
[
"@nx/react/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"plugins": []
}
@@ -0,0 +1,7 @@
# @storyteller/ui-select
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test @storyteller/ui-select` to execute the unit tests via [Vitest](https://vitest.dev/).
@@ -0,0 +1,17 @@
{
"name": "@storyteller/ui-select",
"version": "0.0.1",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"development": "./src/index.ts",
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"default": "./dist/index.js"
}
}
}
@@ -0,0 +1,2 @@
export * from './lib/select';
@@ -0,0 +1,7 @@
/*
* Replace this with your own classes
*
* e.g.
* .container {
* }
*/
@@ -0,0 +1,12 @@
import { render } from '@testing-library/react';
import Select from './select';
describe('Select', () => {
it('should render successfully', () => {
const { baseElement } = render(<Select />);
expect(baseElement).toBeTruthy();
});
});
@@ -0,0 +1,104 @@
import {
Listbox,
ListboxButton,
ListboxOptions,
ListboxOption,
Transition,
} from "@headlessui/react";
import { faCheck, faChevronDown } from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge";
export type SelectValue = string | number;
export type SelectOption = { label: string; value: SelectValue };
interface ListDropdownProps {
options: SelectOption[];
onChange: (val: SelectValue) => void;
placeholder?: string;
value?: SelectValue;
id?: string;
}
export const Select = ({
onChange,
options,
placeholder,
value,
id,
}: ListDropdownProps) => {
const selectedOption = options.find(
(option: SelectOption) => option.value === value
) || { label: placeholder || "", value: "" };
return (
<div className="relative mt-1">
<Listbox value={value || ""} onChange={onChange}>
{({ open }) => (
<>
<ListboxButton
id={id}
className="relative h-10 w-full cursor-pointer rounded-md bg-secondary py-2 pl-3 pr-10 text-left outline-none outline-offset-0 transition-all duration-150 ease-in-out sm:text-sm focus:!outline-none hover:bg-secondary hover:[filter:brightness(1.2)]"
>
<span
className={twMerge("block truncate", !value && "opacity-50")}
>
{selectedOption.label}
</span>
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2.5">
<FontAwesomeIcon icon={faChevronDown} aria-hidden="true" />
</span>
</ListboxButton>
<Transition
as="div"
show={open}
leave="transition ease-in duration-[50ms]"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<ListboxOptions className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-secondary py-1.5 text-base shadow-xl focus:outline-none sm:text-sm">
{options.map((option, itemIdx) => (
<ListboxOption
key={itemIdx}
className={({ focus, selected }) =>
twMerge(
"relative cursor-pointer select-none py-2 pl-7 pr-2 text-white transition-all duration-150 ease-in-out",
focus && "bg-white/[8%] text-white",
selected && "bg-primary/40",
!selected && "text-white/90"
)
}
value={option.value}
>
{({ selected }) => (
<>
<span
className={`block truncate ${
selected ? "font-medium" : "font-normal"
}`}
>
{option.label}
</span>
{selected ? (
<span className="absolute inset-y-0 left-0 flex items-center pl-2.5">
<FontAwesomeIcon
icon={faCheck}
aria-hidden="true"
className="text-xs"
/>
</span>
) : null}
</>
)}
</ListboxOption>
))}
</ListboxOptions>
</Transition>
</>
)}
</Listbox>
</div>
);
};
export default Select;
@@ -0,0 +1,13 @@
{
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"extends": "../../../tsconfig.base.json"
}
@@ -0,0 +1,45 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"types": [
"node",
"@nx/react/typings/cssmodule.d.ts",
"@nx/react/typings/image.d.ts",
"vite/client"
],
"rootDir": "src",
"jsx": "react-jsx",
"tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo"
},
"exclude": [
"out-tsc",
"dist",
"**/*.spec.ts",
"**/*.test.ts",
"**/*.spec.tsx",
"**/*.test.tsx",
"**/*.spec.js",
"**/*.test.js",
"**/*.spec.jsx",
"**/*.test.jsx",
"vite.config.ts",
"vite.config.mts",
"vitest.config.ts",
"vitest.config.mts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx"
],
"include": [
"src/**/*.js",
"src/**/*.jsx",
"src/**/*.ts",
"src/**/*.tsx"
]
}
@@ -0,0 +1,34 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./out-tsc/vitest",
"types": [
"vitest/globals",
"vitest/importMeta",
"vite/client",
"node",
"vitest"
],
"jsx": "react-jsx"
},
"include": [
"vite.config.ts",
"vite.config.mts",
"vitest.config.ts",
"vitest.config.mts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx",
"src/**/*.d.ts"
],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}
@@ -0,0 +1,49 @@
/// <reference types='vitest' />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import dts from 'vite-plugin-dts';
import * as path from 'path';
export default defineConfig(() => ({
root: __dirname,
cacheDir: '../../../node_modules/.vite/libs/components/select',
plugins: [react(), dts({ entryRoot: 'src', tsconfigPath: path.join(__dirname, 'tsconfig.lib.json') })],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
outDir: './dist',
emptyOutDir: true,
reportCompressedSize: true,
commonjsOptions: {
transformMixedEsModules: true,
},
lib: {
// Could also be a dictionary or array of multiple entry points.
entry: 'src/index.ts',
name: '@storyteller/ui-select',
fileName: 'index',
// Change this to the formats you want to support.
// Don't forget to update your package.json as well.
formats: ['es' as const]
},
rollupOptions: {
// External packages that should not be bundled into your library.
external: ['react','react-dom','react/jsx-runtime']
},
},
test: {
watch: false,
globals: true,
environment: 'jsdom',
include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: './test-output/vitest/coverage',
provider: 'v8' as const,
}
},
}));
@@ -0,0 +1,12 @@
{
"presets": [
[
"@nx/react/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"plugins": []
}
@@ -0,0 +1,7 @@
# @storyteller/ui-settings-modal
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test @storyteller/ui-settings-modal` to execute the unit tests via [Vitest](https://vitest.dev/).
@@ -0,0 +1,17 @@
{
"name": "@storyteller/ui-settings-modal",
"version": "0.0.1",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"development": "./src/index.ts",
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"default": "./dist/index.js"
}
}
}
@@ -0,0 +1,2 @@
export * from './lib/settings-modal';
@@ -0,0 +1,7 @@
/*
* Replace this with your own classes
*
* e.g.
* .container {
* }
*/
@@ -0,0 +1,12 @@
import { render } from '@testing-library/react';
import SettingsModal from './settings-modal';
describe('SettingsModal', () => {
it('should render successfully', () => {
const { baseElement } = render(<SettingsModal />);
expect(baseElement).toBeTruthy();
});
});
@@ -0,0 +1,219 @@
import { Modal } from "@storyteller/ui-modal";
import { Button } from "@storyteller/ui-button";
import { useState } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faUser,
faVideo,
faImage,
faCog,
} from "@fortawesome/pro-solid-svg-icons";
import { twMerge } from "tailwind-merge";
import { Input } from "@storyteller/ui-input";
import { Select, SelectValue } from "@storyteller/ui-select";
interface SettingsModalProps {
isOpen: boolean;
onClose: () => void;
}
interface AccountInfo {
username: string;
subscription: string;
credits: number;
}
type SettingsSection = "accounts" | "video" | "image" | "misc";
export const SettingsModal = ({ isOpen, onClose }: SettingsModalProps) => {
const [accountInfo] = useState<AccountInfo>({
username: "Username [logout]",
subscription: "Pro-tier [change/upgrade]",
credits: 10233,
});
const [selectedSection, setSelectedSection] =
useState<SettingsSection>("accounts");
const [palApiKey, setPalApiKey] = useState("");
const [klingApiKey, setKlingApiKey] = useState("");
const [defaultVideoModel, setDefaultVideoModel] = useState("veo");
const [humanVideoProvider, setHumanVideoProvider] = useState("artcraft");
const sections = [
{ id: "accounts" as const, label: "Accounts", icon: faUser },
{ id: "video" as const, label: "Video", icon: faVideo },
{ id: "image" as const, label: "Image", icon: faImage },
{ id: "misc" as const, label: "Misc", icon: faCog },
];
const renderContent = () => {
switch (selectedSection) {
case "accounts":
return (
<div className="space-y-4">
<div className="flex justify-between">
<span>ArtCraft Account:</span>
<span className="text-white/80">{accountInfo.username}</span>
</div>
<div className="flex justify-between">
<span>ArtCraft Subscription:</span>
<span className="text-white/80">{accountInfo.subscription}</span>
</div>
<div className="flex justify-between">
<span>Credits Remaining:</span>
<span className="text-white/80">{accountInfo.credits}</span>
</div>
<div className="rounded-md p-4 text-sm dark:bg-white/5">
<p className="text-white/60">
Note: You can optionally log into other accounts and use your
credits at those providers. Some features are only available via
3rd party accounts, such as OpenAI / Sora GPT 4.0 Images. For
other features, ArtCraft credits are consumed unless you log
into your third party account.
</p>
</div>
<div className="flex justify-between items-center">
<span>OpenAI / Sora Account:</span>
<Button variant="secondary" className="py-1">
Connect
</Button>
</div>
<div className="flex justify-between items-center">
<span>Google / Veo Account:</span>
<Button variant="secondary" className="py-1">
Connect
</Button>
</div>
<div className="space-y-4">
<div>
<label htmlFor="pal-api-key" className="mb-2 block">
Pal API Key (optional)
</label>
<Input
id="pal-api-key"
type="password"
value={palApiKey}
onChange={(e) => setPalApiKey((e.target as any).value)}
placeholder="Enter API Key"
/>
</div>
<div>
<label htmlFor="kling-api-key" className="mb-2 block">
Kling API Key (optional)
</label>
<Input
id="kling-api-key"
type="password"
value={klingApiKey}
onChange={(e) => setKlingApiKey((e.target as any).value)}
placeholder="Enter API Key"
/>
</div>
</div>
</div>
);
case "video":
return (
<div className="space-y-4">
<div>
<label htmlFor="default-video-model" className="mb-2 block">
Default Video Model
</label>
<Select
id="default-video-model"
value={defaultVideoModel}
onChange={(val: SelectValue) =>
setDefaultVideoModel(val as string)
}
options={[
{ value: "veo", label: "Veo" },
{ value: "kling", label: "Kling" },
]}
/>
</div>
<div>
<label htmlFor="human-video-provider" className="mb-2 block">
Human Video Provider
</label>
<Select
id="human-video-provider"
value={humanVideoProvider}
onChange={(val: SelectValue) =>
setHumanVideoProvider(val as string)
}
options={[
{ value: "artcraft", label: "ArtCraft" },
{ value: "pal", label: "Pal" },
]}
/>
</div>
</div>
);
case "image":
return (
<div>
<button className="text-blue-600">Various Image Settings...</button>
</div>
);
case "misc":
return (
<div>
<button className="text-blue-600">Other Settings...</button>
</div>
);
}
};
return (
<Modal
isOpen={isOpen}
onClose={onClose}
className="max-w-3xl"
childPadding={false}
>
<div className="h-[560px]">
<div className="grid h-full grid-cols-12 gap-3">
<div className="relative col-span-4 p-3 pt-2 after:absolute after:right-0 after:top-0 after:h-full after:w-px after:bg-gray-200 after:dark:bg-white/10">
<div className="flex items-center justify-between gap-2.5 py-0.5">
<h2 className="text-[18px] font-semibold opacity-80">Settings</h2>
</div>
<hr className="my-2 w-full border-white/10" />
<div className="space-y-1">
{sections.map((section) => (
<button
key={section.id}
className={twMerge(
"h-9 w-full rounded-lg p-2 text-left transition-colors duration-100 hover:bg-[#63636B]/40",
section.id === selectedSection ? "bg-[#63636B]/40" : ""
)}
onClick={() => setSelectedSection(section.id)}
>
<div className="flex items-center gap-2.5 text-sm">
<FontAwesomeIcon icon={section.icon} />
{section.label}
</div>
</button>
))}
</div>
</div>
<div className="col-span-8 flex h-full flex-col overflow-auto">
<div className="w-full border-b border-white/10 py-2.5 ps-0">
<h2 className="text-[18px] font-semibold">
{sections.find((s) => s.id === selectedSection)?.label}
</h2>
</div>
<div className="p-3 ps-0 text-sm h-full">{renderContent()}</div>
</div>
</div>
</div>
</Modal>
);
};
export default SettingsModal;
@@ -0,0 +1,25 @@
{
"files": [],
"include": [],
"references": [
{
"path": "../select"
},
{
"path": "../input"
},
{
"path": "../button"
},
{
"path": "../modal"
},
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"extends": "../../../tsconfig.base.json"
}
@@ -0,0 +1,59 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"types": [
"node",
"@nx/react/typings/cssmodule.d.ts",
"@nx/react/typings/image.d.ts",
"vite/client"
],
"rootDir": "src",
"jsx": "react-jsx",
"tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo"
},
"exclude": [
"out-tsc",
"dist",
"**/*.spec.ts",
"**/*.test.ts",
"**/*.spec.tsx",
"**/*.test.tsx",
"**/*.spec.js",
"**/*.test.js",
"**/*.spec.jsx",
"**/*.test.jsx",
"vite.config.ts",
"vite.config.mts",
"vitest.config.ts",
"vitest.config.mts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx"
],
"include": [
"src/**/*.js",
"src/**/*.jsx",
"src/**/*.ts",
"src/**/*.tsx"
],
"references": [
{
"path": "../select/tsconfig.lib.json"
},
{
"path": "../input/tsconfig.lib.json"
},
{
"path": "../button/tsconfig.lib.json"
},
{
"path": "../modal/tsconfig.lib.json"
}
]
}
@@ -0,0 +1,34 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./out-tsc/vitest",
"types": [
"vitest/globals",
"vitest/importMeta",
"vite/client",
"node",
"vitest"
],
"jsx": "react-jsx"
},
"include": [
"vite.config.ts",
"vite.config.mts",
"vitest.config.ts",
"vitest.config.mts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx",
"src/**/*.d.ts"
],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}
@@ -0,0 +1,49 @@
/// <reference types='vitest' />
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import dts from 'vite-plugin-dts';
import * as path from 'path';
export default defineConfig(() => ({
root: __dirname,
cacheDir: '../../../node_modules/.vite/libs/components/settings-modal',
plugins: [react(), dts({ entryRoot: 'src', tsconfigPath: path.join(__dirname, 'tsconfig.lib.json') })],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
outDir: './dist',
emptyOutDir: true,
reportCompressedSize: true,
commonjsOptions: {
transformMixedEsModules: true,
},
lib: {
// Could also be a dictionary or array of multiple entry points.
entry: 'src/index.ts',
name: '@storyteller/ui-settings-modal',
fileName: 'index',
// Change this to the formats you want to support.
// Don't forget to update your package.json as well.
formats: ['es' as const]
},
rollupOptions: {
// External packages that should not be bundled into your library.
external: ['react','react-dom','react/jsx-runtime']
},
},
test: {
watch: false,
globals: true,
environment: 'jsdom',
include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: './test-output/vitest/coverage',
provider: 'v8' as const,
}
},
}));
+56
View File
@@ -2022,6 +2022,34 @@
"name": "@storyteller/api",
"version": "0.0.1"
},
"libs/components/button": {
"name": "@storyteller/ui-button",
"version": "0.0.1"
},
"libs/components/close-button": {
"name": "@storyteller/ui-close-button",
"version": "0.0.1"
},
"libs/components/input": {
"name": "@storyteller/ui-input",
"version": "0.0.1"
},
"libs/components/label": {
"name": "@storyteller/ui-label",
"version": "0.0.1"
},
"libs/components/modal": {
"name": "@storyteller/ui-modal",
"version": "0.0.1"
},
"libs/components/select": {
"name": "@storyteller/ui-select",
"version": "0.0.1"
},
"libs/components/settings-modal": {
"name": "@storyteller/ui-settings-modal",
"version": "0.0.1"
},
"libs/components/toaster": {
"name": "@storyteller/ui-toaster",
"version": "0.0.1"
@@ -10036,6 +10064,34 @@
"resolved": "libs/api",
"link": true
},
"node_modules/@storyteller/ui-button": {
"resolved": "libs/components/button",
"link": true
},
"node_modules/@storyteller/ui-close-button": {
"resolved": "libs/components/close-button",
"link": true
},
"node_modules/@storyteller/ui-input": {
"resolved": "libs/components/input",
"link": true
},
"node_modules/@storyteller/ui-label": {
"resolved": "libs/components/label",
"link": true
},
"node_modules/@storyteller/ui-modal": {
"resolved": "libs/components/modal",
"link": true
},
"node_modules/@storyteller/ui-select": {
"resolved": "libs/components/select",
"link": true
},
"node_modules/@storyteller/ui-settings-modal": {
"resolved": "libs/components/settings-modal",
"link": true
},
"node_modules/@storyteller/ui-toaster": {
"resolved": "libs/components/toaster",
"link": true
+21
View File
@@ -17,6 +17,27 @@
},
{
"path": "./libs/components/toaster"
},
{
"path": "./libs/components/close-button"
},
{
"path": "./libs/components/modal"
},
{
"path": "./libs/components/settings-modal"
},
{
"path": "./libs/components/button"
},
{
"path": "./libs/components/input"
},
{
"path": "./libs/components/label"
},
{
"path": "./libs/components/select"
}
]
}