Skip to content
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 .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ jobs:
- run: npm ci

- uses: dtolnay/rust-toolchain@stable
- uses: taiki-e/install-action@v2
with:
components: rustfmt
tool: dioxus-cli

- name: Check formatting
run: npm run format:check
Expand Down
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
},
"editor.formatOnSave": true,
"[rust]": {
"editor.defaultFormatter": "rust-lang.rust-analyzer"
"editor.defaultFormatter": "DioxusLabs.dioxus"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
Expand Down
49 changes: 46 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,48 @@ Reusable inputs in `src/components/inputs.rs`:
- `SwitchInput` - toggle with label
- `NumberInput` - numeric with +/- buttons

## Widget Layout System

Layout classes in `src/pages/widget.css`:

- `.widget` - Flex column container; child `TextAreaForm` elements auto-expand to fill space
- `.widget-grid` - Grid container for widgets without expanding textareas
- `.widget-params` - Horizontal flex-wrap for form controls (inputs grow, buttons/switches don't)
- `.widget-buttons` / `.widget-switches` - Fixed-width groups inside `.widget-params`

**Generator pattern** (params + expanding textarea):

```rust
div { class: "widget",
div { class: "widget-params",
SelectForm::<Mode> { /* ... */ }
NumberInput::<usize> { /* ... */ }
div { class: "widget-buttons", button { /* ... */ } }
div { class: "widget-switches", SwitchInput { /* ... */ } }
}
TextAreaForm { /* expands to fill remaining space */ }
}
```

**Encoder/Decoder pattern** (multiple textareas share space equally):

```rust
div { class: "widget",
TextAreaForm { /* input */ }
TextAreaForm { /* output */ }
}
```

**Converter pattern** (stacked inputs, no expanding):

```rust
div { class: "widget-grid",
SwitchInput { /* ... */ }
TextInput { /* ... */ }
TextInput { /* ... */ }
}
```

## Development

```bash
Expand Down Expand Up @@ -99,6 +141,7 @@ Dark mode is handled by `public/js/darkmode.js`.

1. Create file in appropriate category folder (e.g., `src/pages/converter/my_widget.rs`)
2. Define `WIDGET_ENTRY`, `ICON`, and component function
3. Add module declaration in category's `mod.rs`
4. Add route variant to category's `Route` enum with `#[route("/my-widget")]`
5. Implement match arm in `get_widget_entry()`
3. Choose layout class: `widget` (expanding textareas) or `widget-grid` (stacked inputs)
4. Add module declaration in category's `mod.rs`
5. Add route variant to category's `Route` enum with `#[route("/my-widget")]`
6. Implement match arm in `get_widget_entry()`
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
"build:web": "npm run tailwind:build && dx build --platform web --release",
"build:desktop": "npm run tailwind:build && dx build --platform desktop --release",
"format:prettier": "prettier --write \"**/*.{js,css,md,json,html}\"",
"format:rust": "cargo fmt",
"format:rust": "dx fmt",
"format": "concurrently -n \"prettier,rust\" -c \"yellow,red\" \"npm:format:prettier\" \"npm:format:rust\"",
"format:check": "concurrently -n \"prettier,rust\" -c \"yellow,red\" \"prettier --check '**/*.{js,css,md,json,html}'\" \"cargo fmt --check\"",
"format:check": "concurrently -n \"prettier,rust\" -c \"yellow,red\" \"prettier --check '**/*.{js,css,md,json,html}'\" \"dx fmt --check\"",
"prepare": "husky"
},
"devDependencies": {
Expand All @@ -26,6 +26,6 @@
},
"lint-staged": {
"*.{js,css,md,json,html,yml,yaml}": "prettier --write",
"*.rs": "cargo fmt --"
"*.rs": "sh -c 'for f in \"$@\"; do dx fmt -f \"$f\"; done' _"
}
}
13 changes: 4 additions & 9 deletions src/components/accordion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,14 @@ pub fn Accordion(
div { class: "collapse-title p-2 min-h-0 flex items-center",
// Category link (icon + title) - no tooltip needed when expanded
if let Some(route) = category_route.clone() {
Link {
class: "flex items-center gap-2 grow",
to: route,
Link { class: "flex items-center gap-2 grow", to: route,
if let Some(icon) = icon {
{icon}
}
{title}
}
} else {
div {
class: "flex items-center gap-2 grow",
div { class: "flex items-center gap-2 grow",
if let Some(icon) = icon {
{icon}
}
Expand All @@ -59,7 +56,7 @@ pub fn Accordion(
path {
"fill-rule": "evenodd",
"d": "M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z",
"clip-rule": "evenodd"
"clip-rule": "evenodd",
}
}
}
Expand All @@ -68,9 +65,7 @@ pub fn Accordion(
// Collapsible content (only if has children)
if has_children {
div { class: "collapse-content p-0",
ul { class: "menu w-full",
{children}
}
ul { class: "menu w-full", {children} }
}
}
}
Expand Down
5 changes: 4 additions & 1 deletion src/components/inputs.css
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,11 @@
/* Text Input */
.text-input {
@apply relative;
height: 3.5rem;
}

.text-input input {
@apply input w-full pt-6 pb-2;
@apply input w-full h-full pt-6 pb-2;
}

.text-input label {
Expand All @@ -49,6 +50,7 @@
/* Select Form */
.select-form {
@apply relative;
height: 3.5rem;
}

.select-form select {
Expand All @@ -62,6 +64,7 @@
/* Number Input */
.number-input {
@apply join w-full;
height: 3.5rem;
}

.number-input .number-input-field {
Expand Down
9 changes: 1 addition & 8 deletions src/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,10 @@

/* Layout */
@import "./pages/layout.css";
@import "./pages/widget.css";

/* Pages */
@import "./pages/home_page.css";
@import "./pages/encoder_decoder/base64_encoder.css";
@import "./pages/encoder_decoder/cidr_decoder.css";
@import "./pages/converter/date_converter.css";
@import "./pages/converter/json_yaml_converter.css";
@import "./pages/converter/number_base_converter.css";
@import "./pages/generator/hash_generator.css";
@import "./pages/generator/qr_code_generator.css";
@import "./pages/generator/uuid_generator.css";
@import "./pages/generator/lorem_ipsum.css";
@import "./pages/generator/password_generator.css";
@import "./pages/media/color_picker.css";
18 changes: 10 additions & 8 deletions src/pages/converter/date_converter.css
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
/* Date Converter */
/* Date Converter - uses .widget-grid from widget.css */
@layer components {
.date-converter {
@apply grid gap-y-3;
}

/* Widget-specific: date/time selectors layout */
.date-converter .selectors-wrapper {
@apply flex flex-row gap-y-3 gap-x-2 flex-nowrap;
@apply flex flex-row gap-y-3 gap-x-3 flex-nowrap;
}

.date-converter .selectors {
@apply flex-col;
@apply flex flex-col flex-1;
}

.date-converter .selectors-inner {
@apply flex flex-row flex-nowrap gap-x-2;
@apply flex flex-row flex-nowrap gap-x-3;
}

.date-converter .selectors-inner .number-input {
@apply flex-1;
min-width: 100px;
}

@media screen and (max-width: 835px) {
.date-converter .selectors-wrapper {
@apply flex-wrap;
}

.date-converter .selectors {
@apply w-full;
}
}
}
2 changes: 1 addition & 1 deletion src/pages/converter/date_converter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pub fn DateConverter() -> Element {
let unix_time = date_signal.with(|date_state| date_state.time_utc.unix_timestamp());

rsx! {
div { class: "date-converter",
div { class: "widget-grid date-converter",
SelectForm::<DcTimeZone> {
label: "Time Zone",
oninput: move |tz: DcTimeZone| {
Expand Down
10 changes: 0 additions & 10 deletions src/pages/converter/json_yaml_converter.css

This file was deleted.

20 changes: 11 additions & 9 deletions src/pages/converter/json_yaml_converter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ pub fn JsonYamlConverter() -> Element {
})
});
rsx! {
div { class: "json-yaml-converter",
div { class: "widget",
converter_input { direction: Direction::Json }
converter_input { direction: Direction::Yaml }
}
Expand Down Expand Up @@ -51,17 +51,19 @@ fn converter_input(direction: Direction) -> Element {
match direction {
Direction::Json => {
let yaml_result = convert_json_to_yaml(&input_value);
value_context.set(ConverterValue {
json_value: input_value,
yaml_value: yaml_result,
});
value_context
.set(ConverterValue {
json_value: input_value,
yaml_value: yaml_result,
});
}
Direction::Yaml => {
let json_result = convert_yaml_to_json(&input_value);
value_context.set(ConverterValue {
json_value: json_result,
yaml_value: input_value,
});
value_context
.set(ConverterValue {
json_value: json_result,
yaml_value: input_value,
});
}
};
},
Expand Down
6 changes: 0 additions & 6 deletions src/pages/converter/number_base_converter.css

This file was deleted.

2 changes: 1 addition & 1 deletion src/pages/converter/number_base_converter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub fn NumberBaseConverter() -> Element {
let mut format_number_state = use_context_provider(|| Signal::new(FormatNumberState(false)));

rsx! {
div { class: "number-base-converter",
div { class: "widget-grid",
SwitchInput {
label: "Format Numbers",
checked: format_number_state.read().0,
Expand Down
10 changes: 0 additions & 10 deletions src/pages/encoder_decoder/base64_encoder.css

This file was deleted.

2 changes: 1 addition & 1 deletion src/pages/encoder_decoder/base64_encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub fn Base64Encoder() -> Element {
})
});
rsx! {
div { class: "base64-encoder",
div { class: "widget",
encoder_input { direction: Direction::Encode }
encoder_input { direction: Direction::Decode }
}
Expand Down
10 changes: 0 additions & 10 deletions src/pages/encoder_decoder/cidr_decoder.css

This file was deleted.

2 changes: 1 addition & 1 deletion src/pages/encoder_decoder/cidr_decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ pub fn CidrDecoder() -> Element {

let mut show_error_state = use_signal(|| false);
rsx! {
div { class: "cidr-decoder",
div { class: "widget",
TextInput {
label: "CIDR",
value: "{cidr_input_ref.with(|cidr_str| cidr_str.to_string())}",
Expand Down
10 changes: 0 additions & 10 deletions src/pages/generator/hash_generator.css

This file was deleted.

2 changes: 1 addition & 1 deletion src/pages/generator/hash_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pub fn HashGenerator() -> Element {
});

rsx! {
div { class: "number-base-converter",
div { class: "widget",
SwitchInput {
label: "Uppercase",
checked: hash_generator_state.read().uppercase,
Expand Down
31 changes: 0 additions & 31 deletions src/pages/generator/lorem_ipsum.css

This file was deleted.

Loading