Compare commits

...

5 Commits

Author SHA1 Message Date
Reed Krantz
35e34c27f7 feat(flake): add treefmt for formatting all files 2026-02-18 18:14:12 -06:00
Reed Krantz
6be48da010 fix(flake): add the templates build dependency fix to this project 2026-02-18 17:49:27 -06:00
Reed Krantz
c1b38f27ed feat(components): add a Textarea input for context on EditQuote 2026-01-12 20:26:55 -06:00
Reed Krantz
f1fe0e9827 feat(components): make the quote a Textarea in EditQuote 2026-01-12 20:25:56 -06:00
Reed Krantz
7738a8fa78 feat(components): add the Textarea component 2026-01-12 20:01:32 -06:00
12 changed files with 249 additions and 40 deletions

View File

@@ -2,7 +2,7 @@
name = "squad-quote-store" name = "squad-quote-store"
version = "0.1.0" version = "0.1.0"
authors = ["Reed Krantz <programming@krantz.one>"] authors = ["Reed Krantz <programming@krantz.one>"]
edition = "2021" edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

23
flake.lock generated
View File

@@ -69,7 +69,8 @@
"crane": "crane", "crane": "crane",
"flake-parts": "flake-parts", "flake-parts": "flake-parts",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay" "rust-overlay": "rust-overlay",
"treefmt-nix": "treefmt-nix"
} }
}, },
"rust-overlay": { "rust-overlay": {
@@ -91,6 +92,26 @@
"repo": "rust-overlay", "repo": "rust-overlay",
"type": "github" "type": "github"
} }
},
"treefmt-nix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1770228511,
"narHash": "sha256-wQ6NJSuFqAEmIg2VMnLdCnUc0b7vslUohqqGGD+Fyxk=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "337a4fe074be1042a35086f15481d763b8ddc0e7",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "treefmt-nix",
"type": "github"
}
} }
}, },
"root": "root", "root": "root",

View File

@@ -6,6 +6,11 @@
flake-parts.url = "github:hercules-ci/flake-parts"; flake-parts.url = "github:hercules-ci/flake-parts";
treefmt-nix = {
url = "github:numtide/treefmt-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
crane.url = "github:ipetkov/crane"; crane.url = "github:ipetkov/crane";
rust-overlay = { rust-overlay = {
@@ -17,15 +22,30 @@
outputs = { outputs = {
nixpkgs, nixpkgs,
flake-parts, flake-parts,
treefmt-nix,
crane, crane,
rust-overlay, rust-overlay,
... ...
} @ inputs: } @ inputs:
flake-parts.lib.mkFlake {inherit inputs;} { flake-parts.lib.mkFlake {inherit inputs;} {
imports = [
treefmt-nix.flakeModule
];
systems = ["aarch64-linux" "x86_64-linux" "aarch64-darwin" "x86_64-darwin"]; systems = ["aarch64-linux" "x86_64-linux" "aarch64-darwin" "x86_64-darwin"];
perSystem = {system, ...}: let perSystem = {system, ...}: let
src = ./.; src = craneLib.cleanCargoSource ./.;
# runtime dependencies
buildInputs = with pkgs; [
# openssl
];
# build dependencies
nativeBuildInputs = with pkgs; [
# pkg-config
];
pkgs = import nixpkgs { pkgs = import nixpkgs {
inherit system; inherit system;
@@ -42,42 +62,14 @@
# Build *just* the cargo dependencies, so we can reuse # Build *just* the cargo dependencies, so we can reuse
# all of that work (e.g. via cachix) when running in CI # all of that work (e.g. via cachix) when running in CI
cargoArtifacts = craneLib.buildDepsOnly { cargoArtifacts = craneLib.buildDepsOnly {
inherit src; inherit src buildInputs nativeBuildInputs;
}; };
in rec { in rec {
# Formatter for nix files, available through 'nix fmt'
formatter = pkgs.alejandra;
# Your custom packages
# Accessible through 'nix build', 'nix shell', 'nix run', etc
packages = {
default = craneLib.buildPackage {
inherit src cargoArtifacts;
# runtime dependencies
# buildInputs = [];
# build dependencies
# nativeBuildInputs = [pkgs.pkg-config];
};
};
checks = {
clippy = craneLib.cargoClippy {
inherit src cargoArtifacts;
cargoClippyExtraArgs = "-- --deny warnings";
};
fmt = craneLib.cargoFmt {
inherit src cargoArtifacts;
};
};
# Dev Shell that lets you enter an environment with all the necessary utilites # Dev Shell that lets you enter an environment with all the necessary utilites
# Available through 'nix develop' # Available through 'nix develop'
# Also can be activated automatically if direnv is installed on the system with 'direnv allow' # Also can be activated automatically if direnv is installed on the system with 'direnv allow'
devShells.default = craneLib.devShell { devShells.default = craneLib.devShell {
inherit cargoArtifacts checks; inherit checks;
# extra tooling dependencies # extra tooling dependencies
packages = with pkgs; [ packages = with pkgs; [
@@ -87,6 +79,30 @@
inputsFrom = [packages.default]; inputsFrom = [packages.default];
}; };
# Formatter for nix files, available through 'nix fmt'
treefmt = {
programs.alejandra.enable = true;
programs.rustfmt = {
enable = true;
edition = (builtins.fromTOML (builtins.readFile "${src}/Cargo.toml")).package.edition;
};
};
# Your custom packages
# Accessible through 'nix build', 'nix shell', 'nix run', etc
packages = {
default = craneLib.buildPackage {
inherit src buildInputs nativeBuildInputs cargoArtifacts;
};
};
checks = {
clippy = craneLib.cargoClippy {
inherit src buildInputs nativeBuildInputs cargoArtifacts;
cargoClippyExtraArgs = "-- --deny warnings";
};
};
}; };
}; };
} }

View File

@@ -1,2 +1,2 @@
mod component; mod component;
pub use component::*; pub use component::*;

View File

@@ -1,9 +1,9 @@
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_primitives::{ use dioxus_primitives::{
ContentAlign,
date_picker::{self, DatePickerInputProps, DatePickerProps, DateRangePickerProps}, date_picker::{self, DatePickerInputProps, DatePickerProps, DateRangePickerProps},
popover::{PopoverContentProps, PopoverTriggerProps}, popover::{PopoverContentProps, PopoverTriggerProps},
ContentAlign,
}; };
use super::super::calendar::*; use super::super::calendar::*;

View File

@@ -1,2 +1,2 @@
mod component; mod component;
pub use component::*; pub use component::*;

View File

@@ -1,10 +1,11 @@
use crate::components::{ use crate::components::{
date_picker::{DatePicker, DatePickerInput},
Input, Input,
date_picker::{DatePicker, DatePickerInput},
textarea::Textarea,
}; };
use dioxus::prelude::*; use dioxus::prelude::*;
use time::{macros::offset, OffsetDateTime}; use time::{OffsetDateTime, macros::offset};
#[component] #[component]
pub fn EditQuote() -> Element { pub fn EditQuote() -> Element {
@@ -17,9 +18,10 @@ pub fn EditQuote() -> Element {
.date(), .date(),
) )
}); });
let mut context = use_signal(String::new);
rsx! { rsx! {
Input { Textarea {
oninput: move |e: FormEvent| quote.set(e.value()), oninput: move |e: FormEvent| quote.set(e.value()),
placeholder: "Enter the quote", placeholder: "Enter the quote",
value: quote, value: quote,
@@ -34,5 +36,10 @@ pub fn EditQuote() -> Element {
on_value_change: move |v| date.set(v), on_value_change: move |v| date.set(v),
DatePickerInput {} DatePickerInput {}
} }
Textarea {
oninput: move |e: FormEvent| context.set(e.value()),
placeholder: "Any additional context",
value: context,
}
} }
} }

View File

@@ -17,3 +17,4 @@ pub use edit_quote::EditQuote;
pub mod calendar; pub mod calendar;
pub mod date_picker; pub mod date_picker;
pub mod popover; pub mod popover;
pub mod textarea;

View File

@@ -1,2 +1,2 @@
mod component; mod component;
pub use component::*; pub use component::*;

View File

@@ -0,0 +1,78 @@
use dioxus::prelude::*;
#[derive(Copy, Clone, PartialEq, Default)]
#[non_exhaustive]
pub enum TextareaVariant {
#[default]
Default,
Fade,
Outline,
Ghost,
}
impl TextareaVariant {
pub fn class(&self) -> &'static str {
match self {
TextareaVariant::Default => "default",
TextareaVariant::Fade => "fade",
TextareaVariant::Outline => "outline",
TextareaVariant::Ghost => "ghost",
}
}
}
#[component]
pub fn Textarea(
oninput: Option<EventHandler<FormEvent>>,
onchange: Option<EventHandler<FormEvent>>,
oninvalid: Option<EventHandler<FormEvent>>,
onselect: Option<EventHandler<SelectionEvent>>,
onselectionchange: Option<EventHandler<SelectionEvent>>,
onfocus: Option<EventHandler<FocusEvent>>,
onblur: Option<EventHandler<FocusEvent>>,
onfocusin: Option<EventHandler<FocusEvent>>,
onfocusout: Option<EventHandler<FocusEvent>>,
onkeydown: Option<EventHandler<KeyboardEvent>>,
onkeypress: Option<EventHandler<KeyboardEvent>>,
onkeyup: Option<EventHandler<KeyboardEvent>>,
oncompositionstart: Option<EventHandler<CompositionEvent>>,
oncompositionupdate: Option<EventHandler<CompositionEvent>>,
oncompositionend: Option<EventHandler<CompositionEvent>>,
oncopy: Option<EventHandler<ClipboardEvent>>,
oncut: Option<EventHandler<ClipboardEvent>>,
onpaste: Option<EventHandler<ClipboardEvent>>,
#[props(default)] variant: TextareaVariant,
#[props(extends=GlobalAttributes)]
#[props(extends=textarea)]
attributes: Vec<Attribute>,
children: Element,
) -> Element {
rsx! {
document::Link { rel: "stylesheet", href: asset!("./style.css") }
textarea {
class: "textarea",
"data-slot": "textarea",
"data-style": variant.class(),
oninput: move |e| _ = oninput.map(|callback| callback(e)),
onchange: move |e| _ = onchange.map(|callback| callback(e)),
oninvalid: move |e| _ = oninvalid.map(|callback| callback(e)),
onselect: move |e| _ = onselect.map(|callback| callback(e)),
onselectionchange: move |e| _ = onselectionchange.map(|callback| callback(e)),
onfocus: move |e| _ = onfocus.map(|callback| callback(e)),
onblur: move |e| _ = onblur.map(|callback| callback(e)),
onfocusin: move |e| _ = onfocusin.map(|callback| callback(e)),
onfocusout: move |e| _ = onfocusout.map(|callback| callback(e)),
onkeydown: move |e| _ = onkeydown.map(|callback| callback(e)),
onkeypress: move |e| _ = onkeypress.map(|callback| callback(e)),
onkeyup: move |e| _ = onkeyup.map(|callback| callback(e)),
oncompositionstart: move |e| _ = oncompositionstart.map(|callback| callback(e)),
oncompositionupdate: move |e| _ = oncompositionupdate.map(|callback| callback(e)),
oncompositionend: move |e| _ = oncompositionend.map(|callback| callback(e)),
oncopy: move |e| _ = oncopy.map(|callback| callback(e)),
oncut: move |e| _ = oncut.map(|callback| callback(e)),
onpaste: move |e| _ = onpaste.map(|callback| callback(e)),
..attributes,
{children}
}
}
}

View File

@@ -0,0 +1,2 @@
mod component;
pub use component::*;

View File

@@ -0,0 +1,84 @@
/* Base */
.textarea {
width: 100%;
min-height: 4rem;
box-sizing: border-box;
padding: 8px 12px;
border: none;
border-radius: 0.5rem;
margin: 0;
appearance: none;
background: none;
color: var(--secondary-color-4);
font-family: inherit;
line-height: 1.5;
outline: none;
resize: vertical;
transition: background-color 100ms ease-out, border-color 100ms ease-out, box-shadow 100ms ease-out;
}
.textarea:disabled {
color: var(--secondary-color-5);
cursor: not-allowed;
}
.textarea::placeholder {
color: var(--secondary-color-5);
}
/* Default Variant */
.textarea[data-style="default"] {
background: var(--light, var(--primary-color)) var(--dark, var(--primary-color-3));
box-shadow: inset 0 0 0 1px var(--light, var(--primary-color-6)) var(--dark, var(--primary-color-7));
}
.textarea[data-style="default"]:hover:not(:disabled),
.textarea[data-style="default"]:focus {
background: var(--light, var(--primary-color-4)) var(--dark, var(--primary-color-5));
color: var(--secondary-color-1);
}
/* Fade Variant */
.textarea[data-style="fade"] {
background: var(--light, var(--primary-color)) var(--dark, var(--primary-color-3));
}
.textarea[data-style="fade"]:hover:not(:disabled),
.textarea[data-style="fade"]:focus {
background: var(--light, var(--primary-color-4)) var(--dark, var(--primary-color-5));
color: var(--secondary-color-1);
}
/* Outline Variant */
.textarea[data-style="outline"] {
border: 1px solid var(--primary-color-6);
background-color: var(--light, var(--primary-color))
var(--dark, var(--primary-color-3));
}
.textarea[data-style="outline"]:hover:not(:disabled, :focus) {
border-color: var(--primary-color-7);
}
.textarea[data-style="outline"]:focus {
border-color: var(--focused-border-color);
}
.textarea[data-style="outline"]:invalid,
.textarea[data-style="outline"][aria-invalid="true"] {
border-color: var(--primary-error-color);
}
/* Ghost Variant */
.textarea[data-style="ghost"] {
background-color: transparent;
}
.textarea[data-style="ghost"]:hover:not(:disabled) {
background-color: var(--primary-color-5);
color: var(--secondary-color-1);
}
.textarea[data-style="ghost"]:focus {
border-color: var(--focused-border-color);
}