Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Storybook to Tauri App and Input Component #65

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading