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
10 changes: 9 additions & 1 deletion app/Controllers/ValidationTestController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ class ValidationTestController
{
public function index()
{
if (is_htmx_request()) {
// Only return the form fragment for HTMX requests
return view('partials/validation-test', [
'errors' => [],
'old' => [],
]);
}

return view('validation-test', ['title' => 'ValidationTestController Index']);
}

Expand All @@ -33,4 +41,4 @@ public function handleForm()
'email' => $data['email'],
]);
}
}
}
30 changes: 30 additions & 0 deletions app/Views/Components/navbar.twig
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,37 @@
<li><a href="#">Docs</a></li>
<li><a href="https://github.com/sponsors/yanikkumar"><img src="https://img.shields.io/badge/Sponsor%20Creator-%E2%9D%A4%EF%B8%8F-pink?logo=github-sponsors"/></a></li>
<li><a href="#"><img src="https://img.shields.io/github/stars/sproutphp/framework?style=social&link=https%3A%2F%2Fgithub.com%2FSproutPHP%2Fframework"/></a></li>
<li>
<button id="theme-toggle-btn" aria-label="Toggle dark/light mode" style="background:none;border:none;cursor:pointer;font-size:1.5rem;">
<span id="theme-icon">☀️</span>
</button>
</li>
</ul>
</nav>
<hr/>
</div>

<script>
(function() {
const themeBtn = document.getElementById('theme-toggle-btn');
const themeIcon = document.getElementById('theme-icon');
const html = document.documentElement;
if (!themeBtn || !themeIcon) return;
function setInitialTheme() {
let theme = localStorage.getItem('theme');
if (!theme) {
theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
html.setAttribute('data-theme', theme);
themeIcon.textContent = theme === 'dark' ? '🌙' : '☀️';
}
setInitialTheme();
themeBtn.addEventListener('click', function() {
const currentTheme = html.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
html.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
themeIcon.textContent = newTheme === 'dark' ? '🌙' : '☀️';
});
})();
</script>
29 changes: 29 additions & 0 deletions app/Views/Components/spinner.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{# SproutPHP Component: spinner.twig #}
<div id="spinner" class="htmx-indicator">
<div class="spinner"></div>
</div>
<style>
#spinner {
position: fixed;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
background: rgba(0,0,0,0.7);
color: #fff;
padding: 1rem 2rem;
border-radius: 8px;
z-index: 9999;
}
.spinner {
border: 8px solid #f3f3f3;
border-top: 8px solid #3498db;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto;
}
@keyframes spin {
0% { transform: rotate(0deg);}
100% { transform: rotate(360deg);}
}
</style>
23 changes: 19 additions & 4 deletions app/Views/partials/validation-form.twig
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
{% include "components/spinner.twig" %}
<form
hx-post="/validation-test"
hx-target="#form-container"
hx-swap="innerHTML"
hx-indicator="#spinner"
method="POST"
autocomplete="off"
>
Expand All @@ -11,15 +13,28 @@
<label for="name">Name:</label>
<input type="text" name="name" id="name" value="{{ old.name|e }}">
{% if errors.name %}
<div class="error" style="color: red;">{{ errors.name }}</div>
<div class="error" for="name" style="color: red;">{{ errors.name }}</div>
{% endif %}
</div>
<div>
<label for="email">Email:</label>
<input type="email" name="email" id="email" value="{{ old.email|e }}">
{% if errors.email %}
<div class="error" style="color: red;">{{ errors.email }}</div>
<div class="error" for="email" style="color: red;">{{ errors.email }}</div>
{% endif %}
</div>
<button type="submit">Submit</button>
</form>
<button type="submit">Submit
</button>
</form>
<script>
document.addEventListener('focusin', function(e) {
if (e.target.matches('input[name="name"]')) {
const error = e.target.closest('form').querySelector('.error[for="name"]');
if (error) error.remove();
}
if (e.target.matches('input[name="email"]')) {
const error = e.target.closest('form').querySelector('.error[for="email"]');
if (error) error.remove();
}
});
</script>
10 changes: 4 additions & 6 deletions app/Views/validation-test.twig
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@
{% block title %}validation-test{% endblock %}

{% block content %}
<h1>validation-test</h1>
<p>This is the <strong>validation-test</strong> page.</p>
<h1>Validation Test Form</h1>
<div id="form-container">
{% include "partials/validation-form.twig" %}
</div>
<h1>Validation Test Form</h1>
<div id="form-container">
{% include "partials/validation-form.twig" %}
</div>
{% endblock %}
99 changes: 87 additions & 12 deletions core/Console/PostInstall.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,11 @@ public static function run()
}

$color = '';
$needsColor = in_array($choice, ['5','6','7','8']);
$needsColor = in_array($choice, ['5', '6', '7', '8']);
if ($needsColor) {
echo "Enter color name (amber, blue, cyan, fuchsia, green, grey, indigo, jade, lime, orange, pink, pumpkin, purple, red, sand, slate, violet, yellow, zinc): ";
$color = strtolower(trim(fgets(STDIN)));
$validColors = ['amber','blue','cyan','fuchsia','green','grey','indigo','jade','lime','orange','pink','pumpkin','purple','red','sand','slate','violet','yellow','zinc'];
$validColors = ['amber', 'blue', 'cyan', 'fuchsia', 'green', 'grey', 'indigo', 'jade', 'lime', 'orange', 'pink', 'pumpkin', 'purple', 'red', 'sand', 'slate', 'violet', 'yellow', 'zinc'];
if (!in_array($color, $validColors)) {
echo "Invalid color. Defaulting to blue.\n";
$color = 'blue';
Expand All @@ -78,21 +78,96 @@ public static function run()
$file = '';

switch ($choice) {
case '1': $file = "pico$min.css"; break;
case '2': $file = "pico.classless$min.css"; break;
case '3': $file = "pico.conditional$min.css"; break;
case '4': $file = "pico.fluid.classless$min.css"; break;
case '5': $file = "pico.$color$min.css"; break;
case '6': $file = "pico.classless.$color$min.css"; break;
case '7': $file = "pico.conditional.$color$min.css"; break;
case '8': $file = "pico.fluid.classless.conditional.$color$min.css"; break;
case '9': $file = "pico.colors$min.css"; break;
default: $file = "pico$min.css"; break;
case '1':
$file = "pico$min.css";
break;
case '2':
$file = "pico.classless$min.css";
break;
case '3':
$file = "pico.conditional$min.css";
break;
case '4':
$file = "pico.fluid.classless$min.css";
break;
case '5':
$file = "pico.$color$min.css";
break;
case '6':
$file = "pico.classless.$color$min.css";
break;
case '7':
$file = "pico.conditional.$color$min.css";
break;
case '8':
$file = "pico.fluid.classless.conditional.$color$min.css";
break;
case '9':
$file = "pico.colors$min.css";
break;
default:
$file = "pico$min.css";
break;
}

$url = $base . $file;
$dest = __DIR__ . '/../../public/assets/css/sprout.min.css';

self::download($url, $dest);

// Prompt for dark/light mode toggle
echo "\nWould you like to include a dark/light mode toggle button in your navbar? (y/N): ";
$includeToggle = strtolower(trim(fgets(STDIN)));
if ($includeToggle === 'y') {
$navbarPath = __DIR__ . '/../../app/Views/components/navbar.twig';
$navbar = file_get_contents($navbarPath);
// Only add if not already present
if (strpos($navbar, 'theme-toggle-btn') === false) {
$toggleBtn = <<<HTML
<li>
<!-- Dark/Light mode toggle auto-included by SproutPHP installer -->
<button id="theme-toggle-btn" aria-label="Toggle dark/light mode" style="background:none;border:none;cursor:pointer;font-size:1.5rem;">
<span id="theme-icon">☀️</span>
</button>
</li>
HTML;
// Insert before </ul> of the right-side nav (last </ul> in file)
$navbar = preg_replace('/(<\/ul>)(?![\s\S]*<\/ul>)/', "$toggleBtn\n$1", $navbar, 1);
// Add the script before </div> at the end
$toggleScript = <<<SCRIPT
<script>
(function() {
const themeBtn = document.getElementById('theme-toggle-btn');
const themeIcon = document.getElementById('theme-icon');
const html = document.documentElement;
if (!themeBtn || !themeIcon) return;
function setInitialTheme() {
let theme = localStorage.getItem('theme');
if (!theme) {
theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
html.setAttribute('data-theme', theme);
themeIcon.textContent = theme === 'dark' ? '🌙' : '☀️';
}
setInitialTheme();
themeBtn.addEventListener('click', function() {
const currentTheme = html.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
html.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
themeIcon.textContent = newTheme === 'dark' ? '🌙' : '☀️';
});
})();
</script>
SCRIPT;
$navbar = preg_replace('/<\/div>\s*$/', "$toggleScript\n</div>", $navbar, 1);
file_put_contents($navbarPath, $navbar);
echo "✅ Dark/light mode toggle added to your navbar.\n";
} else {
echo "ℹ️ Dark/light mode toggle already present in your navbar.\n";
}
} else {
echo "ℹ️ You can add a dark/light mode toggle later by yourself if you wish.\n";
}
}
}