Skip to content

Commit

Permalink
Merge pull request #65 from MrVintage710/64-common-wizard-component
Browse files Browse the repository at this point in the history
Added Storybook to Tauri App and Input Component
  • Loading branch information
Gearhartlove authored Nov 12, 2024
2 parents d6be8d0 + 7dc7905 commit 7dd673e
Show file tree
Hide file tree
Showing 19 changed files with 435 additions and 62 deletions.
3 changes: 2 additions & 1 deletion .storybook/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ const theme : VitruvianTheme = {
background_alt: "#2e2e2e",
font_color_primary: "#ffffff",
font_color_secondary: "#909090",
font_primary : "CrimsonPro"
font_primary : "CrimsonPro",
error: "#ff0000"
}

applyTheme(theme);
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"build": "tsc && vite build",
"preview": "vite preview",
"tauri": "tauri",
"storybook": "storybook dev -p 6006",
"storybook": "tauri dev --config src-tauri/storybook.conf.json",
"fmt" : "cargo fmt --manifest-path src-tauri/Cargo.toml & cargo fmt --manifest-path src-types/Cargo.toml & cargo fmt --manifest-path src-dbs/Cargo.toml",
"build-storybook": "storybook build"
},
"dependencies": {
Expand Down
4 changes: 3 additions & 1 deletion src-tauri/src/commands/themes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ pub fn get_current_theme() -> VitruvianTheme {
font_color_primary: "#ffffff".to_string(),
font_color_secondary: "#909090".to_string(),
font_primary: "CrimsonPro".to_string(),
error: "#ff0000".to_string(),
}
}

/// Command for the frontend, returns the theme with the given theme_id.
#[tauri::command]
pub fn get_theme(theme_id: String) -> VitruvianTheme {
// This is a dummy functions for now, will be properly implemented in #30

if &theme_id == "green" {
VitruvianTheme {
primary: "#a5d6a7".to_string(),
Expand All @@ -31,6 +31,7 @@ pub fn get_theme(theme_id: String) -> VitruvianTheme {
font_color_primary: "#ffffff".to_string(),
font_color_secondary: "#909090".to_string(),
font_primary: "CrimsonPro".to_string(),
error: "#ff0000".to_string(),
}
} else if &theme_id == "red" {
VitruvianTheme {
Expand All @@ -42,6 +43,7 @@ pub fn get_theme(theme_id: String) -> VitruvianTheme {
font_color_primary: "#ffffff".to_string(),
font_color_secondary: "#909090".to_string(),
font_primary: "CrimsonPro".to_string(),
error: "#ff0000".to_string(),
}
} else {
get_current_theme()
Expand Down
19 changes: 18 additions & 1 deletion src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ fn set_test_data(data: Value) {
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
pub fn run_main_app() {
tauri::Builder::default()
.plugin(tauri_plugin_shell::init())
.invoke_handler(tauri::generate_handler![
Expand All @@ -45,3 +45,20 @@ pub fn run() {
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

// #[cfg_attr(mobile, tauri::mobile_entry_point)]
// pub fn run_storybook() {
// tauri::Builder::default()
// .plugin(tauri_plugin_shell::init())
// .invoke_handler(tauri::generate_handler![
// greet,
// get_test_data,
// set_test_data,
// themes::get_current_theme,
// themes::get_theme,
// themes::get_available_themes,
// themes::set_current_theme,
// ])
// .run(tauri::generate_context!())
// .expect("error while running tauri application");
// }
2 changes: 1 addition & 1 deletion src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

fn main() {
vitruvian_vtt_lib::run()
vitruvian_vtt_lib::run_main_app()
}
34 changes: 34 additions & 0 deletions src-tauri/storybook.conf.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"productName": "vitruvian_vtt_storybook",
"version": "0.1.0",
"identifier": "com.vitruvian_vtt.storybook.app",
"build": {
"beforeDevCommand": "storybook dev -p 1421 --ci",
"devUrl": "http://localhost:1421",
"beforeBuildCommand": "storybook build",
"frontendDist": "../dist"
},
"app": {
"windows": [
{
"title": "Vitruvian VTT Storybook",
"width": 800,
"height": 600
}
],
"security": {
"csp": null
}
},
"bundle": {
"active": true,
"targets": "all",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/[email protected]",
"icons/icon.icns",
"icons/icon.ico"
]
}
}
1 change: 1 addition & 0 deletions src-types/src/theme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ pub struct VitruvianTheme {
pub font_color_primary: String,
pub font_color_secondary: String,
pub font_primary: String,
pub error: String,
}
1 change: 1 addition & 0 deletions src/common/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export function applyTheme(theme: VitruvianTheme) {
document.documentElement.style.setProperty("--background-alt-color", theme.background_alt);
document.documentElement.style.setProperty("--font-color-primary", theme.font_color_primary);
document.documentElement.style.setProperty("--font-color-secondary", theme.font_color_secondary);
document.documentElement.style.setProperty("--error-color", theme.error);
document.documentElement.style.setProperty("background-color", theme.background);
document.documentElement.style.setProperty("color", theme.font_color_primary);
document.documentElement.style.setProperty("font-family", theme.font_primary);
Expand Down
24 changes: 18 additions & 6 deletions src/components/Modal.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { CSSProperties, useState } from "react"
import { createPortal } from "react-dom"
import { UnitSize, parseUnitSize } from "../common/types"

/** The Props for the modal component */
export type ModalProps = {
/** The content of the modal. This can be any react component or a function that takes in one function and returns any component. The input to that function is a function that closes the model */
children: ((close : () => void) => React.ReactNode) | React.ReactNode,
children?: ((close : () => void) => React.ReactNode) | React.ReactNode,
/** Whether the modal is active or not. */
active: boolean,
/** A function that sets the active state of the value passed into the modal */
Expand All @@ -17,6 +18,8 @@ export type ModalProps = {
noTransition?: boolean,
/** Whether the modal should allow background scrolling or not */
allowBackgroundScroll?: boolean,
width? : UnitSize,
height? : UnitSize
}

/** This is the modal component for Vitruvian VTT. Unlike other Modals, the parrent of the modal is responsible for controlling the active state */
Expand All @@ -27,7 +30,9 @@ export default function Modal({
onOpen = () => {},
onClose = () => {},
noTransition = false,
allowBackgroundScroll = false
allowBackgroundScroll = false,
width = "hug",
height = "hug"
} : ModalProps) {

const [style, setStyle] = useState<CSSProperties>({
Expand All @@ -52,9 +57,16 @@ export default function Modal({
}
}

return active ? createPortal(<div className="fixed top-0 left-0 w-screen h-screen flex justify-center items-center bg-black bg-opacity-45" style={noTransition ? undefined : style} onClick={closeFunction}>
<div className="z-20" onClick={event => event.stopPropagation()}>
{typeof children === "function" ? children(closeFunction) : children}
const pos = {
width : parseUnitSize(width),
height : parseUnitSize(height),
}

return active ? createPortal((
<div className="fixed top-0 left-0 w-screen h-screen flex justify-center items-center bg-black bg-opacity-45 z-40" style={noTransition ? undefined : style} onClick={closeFunction}>
<div style={pos} onClick={event => event.stopPropagation()}>
{typeof children === "function" ? children(closeFunction) : children}
</div>
</div>
</div>, document.body) : null;
), document.body) : null;
}
64 changes: 64 additions & 0 deletions src/components/input/Input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { forwardRef, useState } from "react";
import "./input.css"
import Icon from "../Icon";
import { UnitSize, parseUnitSize } from "../../common/types";

export type InputProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, "className"> & {
/* The width of the input. Defaults to full */
width?: UnitSize;
/* This is a callback that is called when the value of the input changes. This mirrors the `onChange` callback. */
onValueChange? : (value : string) => void;
};

const Input = forwardRef<HTMLInputElement, InputProps>((props: InputProps, ref) => {
const { placeholder, width = "full", value, onValueChange = () => {} } = props;

const onBlur = (event : React.FocusEvent<HTMLInputElement>) => {
props.onBlur && props.onBlur(event);
const value = event.target ? (event.target as HTMLInputElement).value : "";
if(value !== "") {
event.target.classList.add("filled");
} else {
event.target.classList.remove("filled");
}
}

const onChange = (event : React.ChangeEvent<HTMLInputElement>) => {
onValueChange(event.target.value);
props.onChange && props.onChange(event);
}

const widthStyle = width ? {width : parseUnitSize(width)} : {};

return (
<label className="field mt-2 h-full" style={widthStyle}>
<input
autoCapitalize="false"
autoComplete="false"
type="text"
ref={ref}
{...props}
value={value}
className={`${(value !== undefined && value !== "") ? "filled " : ""}text-theme-font-primary bg-theme-background-secondary border border-white rounded-md`}
style={widthStyle}
onBlur={onBlur}
onChange={onChange}
placeholder={undefined}
/>
<span className="placeholder hover:cursor-text">{placeholder}</span>
<span className="error"><Icon variant="warning"/></span>
</label>
);
})

export default Input;

/* This hook is used to manage the state of an input that you use so you don't have to do them yourselves.*/
export function useInput(props : InputProps) : [string, React.Dispatch<React.SetStateAction<string>>, React.ReactNode] {

const value : string = props.value ? Array.isArray(props.value) ? props.value[0] : props.value : "";
const [input, setInput] = useState<string>(value);
const component = <Input {...props} value={input} onValueChange={(value) => setInput(value)}/>;

return [input, setInput, component]
}
62 changes: 62 additions & 0 deletions src/components/input/input.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
.field {
--animation-duration : 0.2s;
position: relative;
border: none;
appearance: none;
outline: none;
margin: 0.5rem;
display: inline-block;
}

.field input {
padding: 0.5rem;
padding-right: calc(0.5rem + 28px);
padding-left: 1.0rem;
font-size: 16pt;
background-color: var(--background-color);
outline: none;
user-select: none;
}

.field .placeholder {
position: absolute;
left: 1.0rem;
top: 50%;
transform: translateY(-50%);
color: var(--font-color-secondary);
font-size: 16pt;
background-color: var(--background-color);
padding: 0 5px;
transition: top var(--animation-duration) ease-in-out, font-size var(--animation-duration) ease-in-out, transform var(--animation-duration) ease-in-out;
user-select: none;
border-radius: 5px;
}

.field input.filled + .placeholder,
.field input:focus + .placeholder {
top: 0;
/* transform: translateY(calc(-16pt - 0.5rem)); */
font-size: 12pt;
}

.field input:invalid ~ .error {
visibility: visible;;

}

.field input:invalid {
border-color: var(--error-color);
}

.field input:invalid + .placeholder {
color: var(--error-color);
}

.field .error {
position: absolute;
top: calc(50% + 2px);
transform: translateY(-50%);
fill: var(--error-color);
right:1.0rem;
visibility: hidden;
}
66 changes: 66 additions & 0 deletions src/components/wizard/WizardCompnent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { useState } from "react"
import {WizardComponentContext} from "./WizardComponentContext"

export type WizardComponentProps<T extends object> = {
startingData: T;
onWizardAbort : () => void;
onWizardComplete? : (data: T) => void;
children: React.ReactNode | React.ReactNode[];
}

export type WizardStepHandler<T> = {
data : T,
abort: () => void,
submitStep : (data : T) => void,
backStep : () => void
}

const WizardComponent = <T extends object>({
children,
startingData,
onWizardComplete = () => {},
onWizardAbort
} : WizardComponentProps<T>) => {
const [currentStep, setCurrentStep] = useState(0);
const [data, setData] = useState<T>(startingData);

let childrenArray = Array.isArray(children) ? children : [children];

if(currentStep >= childrenArray.length) {
onWizardComplete(data);
}

const onSubmitStep = (d : T) => {
setData({...data, ...d});
setCurrentStep(currentStep + 1);
}

const onBackStep = () => {
setCurrentStep(currentStep - 1);
}

const onAbortProcess = () => {
setCurrentStep(0)
onWizardAbort();
}

return (
<WizardComponentContext.Provider value={{ data: data, submitStep : onSubmitStep, backStep : onBackStep, abort: onAbortProcess}}>
<div className="relative w-full h-full">
{childrenArray.map((child, index) => {
const currentPos = (index - currentStep) * 100;
const style = {
transform : `translateX(calc(${currentPos}vw - 50%)) translateY(-50%)`,
left : `50%`,
top : '50%'
}
return <div style={style} className={`absolute transition-all duration-300 ${currentStep === index ? "opacity-100" : "opacity-0"}`}>
{child}
</div>
})}
</div>
</WizardComponentContext.Provider>
)
}

export default WizardComponent;
Loading

0 comments on commit 7dd673e

Please sign in to comment.